@surething/cockpit 1.0.216 → 1.0.218
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/.next-prod/BUILD_ID +1 -1
- package/.next-prod/app-path-routes-manifest.json +2 -2
- package/.next-prod/build-manifest.json +2 -2
- package/.next-prod/prerender-manifest.json +3 -3
- package/.next-prod/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/_global-error.html +1 -1
- package/.next-prod/server/app/_global-error.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/_not-found.html +1 -1
- package/.next-prod/server/app/_not-found.rsc +3 -3
- package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next-prod/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/.next-prod/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next-prod/server/app/api/chat/deepseek/route.js +1 -1
- package/.next-prod/server/app/api/chat/route.js +1 -1
- package/.next-prod/server/app/api/extension/version/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/file-functions/route.js +1 -1
- package/.next-prod/server/app/api/scheduled-tasks/route.js +1 -1
- package/.next-prod/server/app/api/terminal/bubble-order/route.js +1 -1
- package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app-paths-manifest.json +2 -2
- package/.next-prod/server/chunks/2939.js +1 -1
- package/.next-prod/server/chunks/8916.js +1 -1
- package/.next-prod/server/chunks/9658.js +7 -7
- package/.next-prod/server/chunks/9877.js +1 -1
- package/.next-prod/server/functions-config-manifest.json +1 -0
- package/.next-prod/server/middleware-build-manifest.js +1 -1
- package/.next-prod/server/pages/404.html +1 -1
- package/.next-prod/server/pages/500.html +1 -1
- package/.next-prod/server/server-reference-manifest.json +1 -1
- package/.next-prod/static/chunks/6345-2637497e8b101740.js +14 -0
- package/.next-prod/static/chunks/6917-ed0e9c62a123d529.js +29 -0
- package/.next-prod/static/chunks/app/{layout-a0362651ba6e6e6f.js → layout-1659a95e6c4a6bb5.js} +1 -1
- package/.next-prod/static/chunks/app/{page-1b14cabf47df9ff7.js → page-afcbd897b4c3600f.js} +1 -1
- package/.next-prod/static/chunks/app/project/{page-1b14cabf47df9ff7.js → page-afcbd897b4c3600f.js} +1 -1
- package/.next-prod/static/css/f4a773117ca8af75.css +1 -0
- package/.next-prod/trace +13 -13
- package/.next-prod/trace-build +1 -1
- package/README.md +8 -7
- package/README.zh.md +8 -7
- package/bin/cock-browser.messages.mjs +176 -0
- package/bin/cock-browser.mjs +290 -18
- package/bin/cock-codegraph.mjs +21 -6
- package/bin/cock-connection.mjs +151 -0
- package/bin/cock.mjs +12 -1
- package/bin/setup-dev.mjs +15 -13
- package/chrome-extension/automation.js +684 -32
- package/chrome-extension/manifest.json +1 -1
- package/chrome-extension/messages.js +45 -0
- package/dist/{chunk-CZWJPTRO.mjs → chunk-GCYLMG43.mjs} +2486 -1047
- package/dist/chunk-O4P2J44N.mjs +314 -0
- package/dist/{chunk-KRTISG5I.mjs → chunk-WOM47O75.mjs} +245 -10
- package/dist/httpApi.mjs +140 -7
- package/dist/scheduledTasks.mjs +15 -1159
- package/dist/{server-OSOMFNXR.mjs → server-SNB4H35J.mjs} +8 -2
- package/dist/wsServer.mjs +27 -19
- package/package.json +3 -5
- package/server.mjs +5 -1
- package/.next-prod/static/chunks/5188-415582403ef0e29c.js +0 -29
- package/.next-prod/static/chunks/6345-e5ceeb2aeb698eb6.js +0 -14
- package/.next-prod/static/css/cc6d733cdf607b30.css +0 -1
- package/dist/chunk-ZJ6CC3MH.mjs +0 -223
- /package/.next-prod/static/{GAYKr2BmQpFqJgRJfvQ3D → bOkuiIr_nWzG5GjPLNqdN}/_buildManifest.js +0 -0
- /package/.next-prod/static/{GAYKr2BmQpFqJgRJfvQ3D → bOkuiIr_nWzG5GjPLNqdN}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GLOBAL_STATE_FILE,
|
|
3
|
+
findCodexSessionPath,
|
|
4
|
+
findKimiSessionPath,
|
|
5
|
+
getClaude2SessionPath,
|
|
6
|
+
getClaudeSessionPath,
|
|
7
|
+
getOllamaSessionPath,
|
|
8
|
+
readJsonFile,
|
|
9
|
+
withFileLock,
|
|
10
|
+
writeJsonFile
|
|
11
|
+
} from "./chunk-GCYLMG43.mjs";
|
|
12
|
+
|
|
13
|
+
// packages/feature/agent/src/server/state/globalState.ts
|
|
14
|
+
import { createReadStream, existsSync } from "fs";
|
|
15
|
+
import { createInterface } from "readline";
|
|
16
|
+
var MAX_SESSIONS = 15;
|
|
17
|
+
var MAX_TEXT_LEN = 50;
|
|
18
|
+
function truncate(s) {
|
|
19
|
+
if (!s) return s;
|
|
20
|
+
const chars = [...s];
|
|
21
|
+
return chars.length <= MAX_TEXT_LEN ? s : chars.slice(0, MAX_TEXT_LEN).join("") + "\u2026";
|
|
22
|
+
}
|
|
23
|
+
async function updateGlobalState(cwd, sessionId, status, title, lastUserMessage) {
|
|
24
|
+
if (!existsSync(cwd)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
return withFileLock(GLOBAL_STATE_FILE, async () => {
|
|
28
|
+
const state = await readJsonFile(GLOBAL_STATE_FILE, { sessions: [] });
|
|
29
|
+
for (const s of state.sessions) {
|
|
30
|
+
if (!s.status) {
|
|
31
|
+
const legacy = s;
|
|
32
|
+
s.status = legacy.isLoading ? "loading" : "normal";
|
|
33
|
+
delete legacy.isLoading;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const existingIndex = state.sessions.findIndex(
|
|
37
|
+
(s) => s.cwd === cwd && s.sessionId === sessionId
|
|
38
|
+
);
|
|
39
|
+
const existed = existingIndex >= 0;
|
|
40
|
+
const existing = existed ? state.sessions[existingIndex] : void 0;
|
|
41
|
+
const newSession = {
|
|
42
|
+
cwd,
|
|
43
|
+
sessionId,
|
|
44
|
+
lastActive: Date.now(),
|
|
45
|
+
status,
|
|
46
|
+
title: truncate(title || existing?.title),
|
|
47
|
+
lastUserMessage: truncate(lastUserMessage || existing?.lastUserMessage)
|
|
48
|
+
};
|
|
49
|
+
if (existingIndex >= 0) {
|
|
50
|
+
state.sessions[existingIndex] = newSession;
|
|
51
|
+
} else {
|
|
52
|
+
state.sessions.push(newSession);
|
|
53
|
+
}
|
|
54
|
+
state.sessions.sort((a, b) => b.lastActive - a.lastActive);
|
|
55
|
+
state.sessions = state.sessions.slice(0, MAX_SESSIONS);
|
|
56
|
+
await writeJsonFile(GLOBAL_STATE_FILE, state);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async function getSessionTitle(cwd, sessionId) {
|
|
60
|
+
const claudePath = getClaudeSessionPath(cwd, sessionId);
|
|
61
|
+
if (existsSync(claudePath)) {
|
|
62
|
+
return getClaudeStyleTitle(claudePath);
|
|
63
|
+
}
|
|
64
|
+
const claude2Path = getClaude2SessionPath(cwd, sessionId);
|
|
65
|
+
if (existsSync(claude2Path)) {
|
|
66
|
+
return getClaudeStyleTitle(claude2Path);
|
|
67
|
+
}
|
|
68
|
+
const ollamaPath = getOllamaSessionPath(cwd, sessionId);
|
|
69
|
+
if (existsSync(ollamaPath)) {
|
|
70
|
+
return getClaudeStyleTitle(ollamaPath);
|
|
71
|
+
}
|
|
72
|
+
const codexPath = findCodexSessionPath(sessionId);
|
|
73
|
+
if (codexPath && existsSync(codexPath)) {
|
|
74
|
+
const title = await getCodexTitle(codexPath);
|
|
75
|
+
return title || "Untitled Session";
|
|
76
|
+
}
|
|
77
|
+
const kimiPath = findKimiSessionPath(sessionId);
|
|
78
|
+
if (kimiPath && existsSync(kimiPath)) {
|
|
79
|
+
const title = await getKimiTitle(kimiPath);
|
|
80
|
+
return title || "Untitled Session";
|
|
81
|
+
}
|
|
82
|
+
return "Untitled Session";
|
|
83
|
+
}
|
|
84
|
+
async function getLastUserMessage(cwd, sessionId) {
|
|
85
|
+
const claudePath = getClaudeSessionPath(cwd, sessionId);
|
|
86
|
+
if (existsSync(claudePath)) {
|
|
87
|
+
return await getClaudeStyleLastUserMessage(claudePath);
|
|
88
|
+
}
|
|
89
|
+
const claude2Path = getClaude2SessionPath(cwd, sessionId);
|
|
90
|
+
if (existsSync(claude2Path)) {
|
|
91
|
+
return await getClaudeStyleLastUserMessage(claude2Path);
|
|
92
|
+
}
|
|
93
|
+
const ollamaPath = getOllamaSessionPath(cwd, sessionId);
|
|
94
|
+
if (existsSync(ollamaPath)) {
|
|
95
|
+
return await getClaudeStyleLastUserMessage(ollamaPath);
|
|
96
|
+
}
|
|
97
|
+
const codexPath = findCodexSessionPath(sessionId);
|
|
98
|
+
if (codexPath && existsSync(codexPath)) {
|
|
99
|
+
return await getCodexLastUserMessage(codexPath);
|
|
100
|
+
}
|
|
101
|
+
const kimiPath = findKimiSessionPath(sessionId);
|
|
102
|
+
if (kimiPath && existsSync(kimiPath)) {
|
|
103
|
+
return await getKimiLastUserMessage(kimiPath);
|
|
104
|
+
}
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
function filterCommandTags(text) {
|
|
108
|
+
let filtered = text.replace(/<command-[^>]*>[\s\S]*?<\/command-[^>]*>/g, "");
|
|
109
|
+
filtered = filtered.replace(/<local-command-[^>]*>[\s\S]*?<\/local-command-[^>]*>/g, "");
|
|
110
|
+
filtered = filtered.trim();
|
|
111
|
+
return filtered;
|
|
112
|
+
}
|
|
113
|
+
function isValidUserMessage(text) {
|
|
114
|
+
if (text.startsWith("This session is being continued")) return false;
|
|
115
|
+
if (text.startsWith("Caveat: The messages below")) return false;
|
|
116
|
+
if (!text.trim()) return false;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
async function getClaudeStyleTitle(filePath) {
|
|
120
|
+
try {
|
|
121
|
+
const fileStream = createReadStream(filePath);
|
|
122
|
+
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
123
|
+
let summary = "";
|
|
124
|
+
const userMessages = [];
|
|
125
|
+
for await (const line of rl) {
|
|
126
|
+
if (!line.trim()) continue;
|
|
127
|
+
try {
|
|
128
|
+
const entry = JSON.parse(line);
|
|
129
|
+
if (entry.type === "summary" && entry.summary) {
|
|
130
|
+
summary = entry.summary;
|
|
131
|
+
}
|
|
132
|
+
if (entry.type === "user") {
|
|
133
|
+
const message = entry.message;
|
|
134
|
+
if (!message?.content) continue;
|
|
135
|
+
if (typeof message.content === "string") {
|
|
136
|
+
userMessages.push(message.content);
|
|
137
|
+
} else if (Array.isArray(message.content)) {
|
|
138
|
+
for (const block of message.content) {
|
|
139
|
+
if (block.type === "text" && block.text) userMessages.push(block.text);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return generateTitle(summary, userMessages);
|
|
147
|
+
} catch {
|
|
148
|
+
return "Untitled Session";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function getClaudeStyleLastUserMessage(filePath) {
|
|
152
|
+
try {
|
|
153
|
+
const fileStream = createReadStream(filePath);
|
|
154
|
+
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
155
|
+
let lastUserMessage;
|
|
156
|
+
for await (const line of rl) {
|
|
157
|
+
if (!line.trim()) continue;
|
|
158
|
+
try {
|
|
159
|
+
const entry = JSON.parse(line);
|
|
160
|
+
if (entry.type !== "user") continue;
|
|
161
|
+
const message = entry.message;
|
|
162
|
+
if (!message?.content) continue;
|
|
163
|
+
let text = "";
|
|
164
|
+
if (typeof message.content === "string") {
|
|
165
|
+
text = message.content;
|
|
166
|
+
} else if (Array.isArray(message.content)) {
|
|
167
|
+
for (const block of message.content) {
|
|
168
|
+
if (block.type === "text" && block.text) {
|
|
169
|
+
text = block.text;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (!text) continue;
|
|
175
|
+
const filtered = filterCommandTags(text);
|
|
176
|
+
if (filtered && isValidUserMessage(filtered)) {
|
|
177
|
+
lastUserMessage = filtered;
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return lastUserMessage;
|
|
183
|
+
} catch {
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function getCodexLastUserMessage(filePath) {
|
|
188
|
+
try {
|
|
189
|
+
const fileStream = createReadStream(filePath);
|
|
190
|
+
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
191
|
+
let last;
|
|
192
|
+
for await (const line of rl) {
|
|
193
|
+
if (!line.trim()) continue;
|
|
194
|
+
let entry;
|
|
195
|
+
try {
|
|
196
|
+
entry = JSON.parse(line);
|
|
197
|
+
} catch {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (entry.type !== "response_item") continue;
|
|
201
|
+
const payload = entry.payload;
|
|
202
|
+
if (!payload || payload.type !== "message" || payload.role !== "user") continue;
|
|
203
|
+
const text = payload.content?.filter((c) => c.type === "input_text" && c.text).map((c) => c.text).join("") || "";
|
|
204
|
+
if (!text || text.startsWith("<") || text.startsWith("#")) continue;
|
|
205
|
+
const filtered = filterCommandTags(text);
|
|
206
|
+
if (filtered && isValidUserMessage(filtered)) {
|
|
207
|
+
last = filtered;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return last;
|
|
211
|
+
} catch {
|
|
212
|
+
return void 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async function getCodexTitle(filePath) {
|
|
216
|
+
try {
|
|
217
|
+
const fileStream = createReadStream(filePath);
|
|
218
|
+
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
219
|
+
for await (const line of rl) {
|
|
220
|
+
if (!line.trim()) continue;
|
|
221
|
+
let entry;
|
|
222
|
+
try {
|
|
223
|
+
entry = JSON.parse(line);
|
|
224
|
+
} catch {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (entry.type !== "response_item") continue;
|
|
228
|
+
const payload = entry.payload;
|
|
229
|
+
if (!payload || payload.type !== "message" || payload.role !== "user") continue;
|
|
230
|
+
const text = payload.content?.filter((c) => c.type === "input_text" && c.text).map((c) => c.text).join("") || "";
|
|
231
|
+
if (!text || text.startsWith("<") || text.startsWith("#")) continue;
|
|
232
|
+
return text.slice(0, 80);
|
|
233
|
+
}
|
|
234
|
+
return void 0;
|
|
235
|
+
} catch {
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function getKimiLastUserMessage(filePath) {
|
|
240
|
+
try {
|
|
241
|
+
const fileStream = createReadStream(filePath);
|
|
242
|
+
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
243
|
+
let last;
|
|
244
|
+
for await (const line of rl) {
|
|
245
|
+
if (!line.trim()) continue;
|
|
246
|
+
let entry;
|
|
247
|
+
try {
|
|
248
|
+
entry = JSON.parse(line);
|
|
249
|
+
} catch {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (entry.role !== "user") continue;
|
|
253
|
+
const text = typeof entry.content === "string" ? entry.content : Array.isArray(entry.content) ? entry.content.filter((c) => (c.type === "input_text" || c.type === "text") && c.text).map((c) => c.text).join("") : "";
|
|
254
|
+
if (!text || text.startsWith("<system") || text.startsWith("<environment") || text.startsWith("# AGENTS.md") || text.startsWith("<permissions")) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const filtered = filterCommandTags(text);
|
|
258
|
+
if (filtered && isValidUserMessage(filtered)) {
|
|
259
|
+
last = filtered;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return last;
|
|
263
|
+
} catch {
|
|
264
|
+
return void 0;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function getKimiTitle(filePath) {
|
|
268
|
+
try {
|
|
269
|
+
const fileStream = createReadStream(filePath);
|
|
270
|
+
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
271
|
+
for await (const line of rl) {
|
|
272
|
+
if (!line.trim()) continue;
|
|
273
|
+
let entry;
|
|
274
|
+
try {
|
|
275
|
+
entry = JSON.parse(line);
|
|
276
|
+
} catch {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (entry.role !== "user") continue;
|
|
280
|
+
const text = typeof entry.content === "string" ? entry.content : Array.isArray(entry.content) ? entry.content.filter((c) => (c.type === "input_text" || c.type === "text") && c.text).map((c) => c.text).join("") : "";
|
|
281
|
+
if (!text || text.startsWith("<system") || text.startsWith("<environment") || text.startsWith("# AGENTS.md") || text.startsWith("<permissions")) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
return text.slice(0, 80);
|
|
285
|
+
}
|
|
286
|
+
return void 0;
|
|
287
|
+
} catch {
|
|
288
|
+
return void 0;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function generateTitle(summary, userMessages) {
|
|
292
|
+
if (summary) return summary;
|
|
293
|
+
let commandName = "";
|
|
294
|
+
for (const msg of userMessages) {
|
|
295
|
+
const filtered = filterCommandTags(msg);
|
|
296
|
+
if (!filtered) continue;
|
|
297
|
+
if (filtered.startsWith("/") && !commandName) {
|
|
298
|
+
commandName = filtered;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (commandName) {
|
|
302
|
+
return `${commandName} ${filtered}`;
|
|
303
|
+
}
|
|
304
|
+
return filtered;
|
|
305
|
+
}
|
|
306
|
+
if (commandName) return commandName;
|
|
307
|
+
return "Untitled Session";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export {
|
|
311
|
+
updateGlobalState,
|
|
312
|
+
getSessionTitle,
|
|
313
|
+
getLastUserMessage
|
|
314
|
+
};
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AppRuntime,
|
|
3
|
+
Cause_exports,
|
|
4
|
+
Effect_exports,
|
|
5
|
+
Exit_exports,
|
|
6
|
+
FSError,
|
|
7
|
+
Option_exports,
|
|
8
|
+
ValidationError,
|
|
2
9
|
ensureParentDir,
|
|
10
|
+
errorToStatus,
|
|
11
|
+
getBubbleOrderPath,
|
|
3
12
|
getTerminalHistoryPath,
|
|
4
|
-
getTerminalOutputPath
|
|
5
|
-
|
|
13
|
+
getTerminalOutputPath,
|
|
14
|
+
readJsonFile,
|
|
15
|
+
writeJsonFile
|
|
16
|
+
} from "./chunk-GCYLMG43.mjs";
|
|
6
17
|
|
|
7
18
|
// packages/feature/console/src/server/plugins/browser/BrowserBridge.ts
|
|
8
19
|
import { WebSocket } from "ws";
|
|
@@ -33,9 +44,9 @@ function toShortId(fullId) {
|
|
|
33
44
|
var g_browser = globalThis;
|
|
34
45
|
var registry = g_browser.__cockpitBrowserRegistry ?? (g_browser.__cockpitBrowserRegistry = /* @__PURE__ */ new Map());
|
|
35
46
|
var fullIdToShort = g_browser.__cockpitBrowserFullIdToShort ?? (g_browser.__cockpitBrowserFullIdToShort = /* @__PURE__ */ new Map());
|
|
36
|
-
function registerBrowser(fullId, ws) {
|
|
47
|
+
function registerBrowser(fullId, ws, projectCwd, tabId) {
|
|
37
48
|
const shortId = toShortId(fullId);
|
|
38
|
-
registry.set(shortId, { fullId, ws, lastSeen: Date.now() });
|
|
49
|
+
registry.set(shortId, { fullId, ws, lastSeen: Date.now(), projectCwd, tabId });
|
|
39
50
|
fullIdToShort.set(fullId, shortId);
|
|
40
51
|
return shortId;
|
|
41
52
|
}
|
|
@@ -65,7 +76,9 @@ function listBrowsers() {
|
|
|
65
76
|
result.push({
|
|
66
77
|
shortId,
|
|
67
78
|
fullId: entry.fullId,
|
|
68
|
-
connected: entry.ws !== null && entry.ws.readyState === WebSocket.OPEN
|
|
79
|
+
connected: entry.ws !== null && entry.ws.readyState === WebSocket.OPEN,
|
|
80
|
+
projectCwd: entry.projectCwd,
|
|
81
|
+
tabId: entry.tabId
|
|
69
82
|
});
|
|
70
83
|
}
|
|
71
84
|
return result;
|
|
@@ -75,22 +88,28 @@ function createPendingRequest(reqId, timeout) {
|
|
|
75
88
|
return new Promise((resolve, reject) => {
|
|
76
89
|
const timer = setTimeout(() => {
|
|
77
90
|
pendingRequests.delete(reqId);
|
|
91
|
+
recordCommandResolved(reqId, false);
|
|
78
92
|
reject(new Error(`Timeout after ${timeout}ms`));
|
|
79
93
|
}, timeout);
|
|
80
94
|
pendingRequests.set(reqId, { resolve, reject, timer });
|
|
81
95
|
});
|
|
82
96
|
}
|
|
83
|
-
function resolvePendingRequest(reqId,
|
|
97
|
+
function resolvePendingRequest(reqId, ok2, data, error) {
|
|
84
98
|
const pending = pendingRequests.get(reqId);
|
|
85
|
-
if (!pending)
|
|
99
|
+
if (!pending) {
|
|
100
|
+
recordCommandResolved(reqId, ok2);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
86
103
|
clearTimeout(pending.timer);
|
|
87
104
|
pendingRequests.delete(reqId);
|
|
88
|
-
|
|
105
|
+
recordCommandResolved(reqId, ok2);
|
|
106
|
+
if (ok2) {
|
|
89
107
|
pending.resolve(data);
|
|
90
108
|
} else {
|
|
91
109
|
pending.reject(new Error(error || "Browser command failed"));
|
|
92
110
|
}
|
|
93
111
|
}
|
|
112
|
+
var reqIdMeta = globalThis.__cockpitBrowserReqMeta ?? (globalThis.__cockpitBrowserReqMeta = /* @__PURE__ */ new Map());
|
|
94
113
|
function sendCommandToBrowser(shortId, reqId, action, params) {
|
|
95
114
|
const entry = registry.get(shortId);
|
|
96
115
|
if (!entry || !entry.ws || entry.ws.readyState !== WebSocket.OPEN) {
|
|
@@ -102,8 +121,48 @@ function sendCommandToBrowser(shortId, reqId, action, params) {
|
|
|
102
121
|
action,
|
|
103
122
|
params
|
|
104
123
|
}));
|
|
124
|
+
reqIdMeta.set(reqId, { shortId, action });
|
|
105
125
|
return true;
|
|
106
126
|
}
|
|
127
|
+
function recordCommandResolved(reqId, ok2) {
|
|
128
|
+
const meta = reqIdMeta.get(reqId);
|
|
129
|
+
if (!meta) return;
|
|
130
|
+
reqIdMeta.delete(reqId);
|
|
131
|
+
if (ok2) {
|
|
132
|
+
const entry = registry.get(meta.shortId);
|
|
133
|
+
if (entry) {
|
|
134
|
+
entry.lastSuccessTs = Date.now();
|
|
135
|
+
entry.lastSuccessAction = meta.action;
|
|
136
|
+
entry.lastSeen = Date.now();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function getBrowserHealth(shortId) {
|
|
141
|
+
const entry = registry.get(shortId);
|
|
142
|
+
if (!entry) {
|
|
143
|
+
return {
|
|
144
|
+
found: false,
|
|
145
|
+
ws: "unknown",
|
|
146
|
+
lastSeenMs: null,
|
|
147
|
+
lastSuccessMs: null,
|
|
148
|
+
lastSuccessAction: null,
|
|
149
|
+
pendingCommands: 0
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
let pending = 0;
|
|
154
|
+
for (const reqId of pendingRequests.keys()) {
|
|
155
|
+
if (reqIdMeta.get(reqId)?.shortId === shortId) pending += 1;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
found: true,
|
|
159
|
+
ws: entry.ws && entry.ws.readyState === WebSocket.OPEN ? "open" : "closed",
|
|
160
|
+
lastSeenMs: entry.lastSeen ? now - entry.lastSeen : null,
|
|
161
|
+
lastSuccessMs: entry.lastSuccessTs ? now - entry.lastSuccessTs : null,
|
|
162
|
+
lastSuccessAction: entry.lastSuccessAction ?? null,
|
|
163
|
+
pendingCommands: pending
|
|
164
|
+
};
|
|
165
|
+
}
|
|
107
166
|
|
|
108
167
|
// packages/feature/console/src/server/plugins/jupyter/JupyterKernelManager.ts
|
|
109
168
|
import { spawn } from "child_process";
|
|
@@ -440,7 +499,8 @@ function listTerminals(getRunning) {
|
|
|
440
499
|
tabId: entry.tabId,
|
|
441
500
|
command: entry.command,
|
|
442
501
|
pid: cmd?.pid ?? 0,
|
|
443
|
-
running: !!cmd
|
|
502
|
+
running: !!cmd,
|
|
503
|
+
projectCwd: entry.projectCwd
|
|
444
504
|
});
|
|
445
505
|
}
|
|
446
506
|
return result;
|
|
@@ -926,6 +986,178 @@ function grepOutput(cmd, pattern, opts = {}) {
|
|
|
926
986
|
};
|
|
927
987
|
}
|
|
928
988
|
|
|
989
|
+
// packages/shared/effect-runtime/src/next.ts
|
|
990
|
+
var ok = (body, status = 200) => new Response(JSON.stringify(body), {
|
|
991
|
+
status,
|
|
992
|
+
headers: { "content-type": "application/json" }
|
|
993
|
+
});
|
|
994
|
+
var extractErrorMessage = (e) => {
|
|
995
|
+
const maybeMsg = e.message;
|
|
996
|
+
if (typeof maybeMsg === "string" && maybeMsg.length > 0) {
|
|
997
|
+
return maybeMsg;
|
|
998
|
+
}
|
|
999
|
+
const cause = e.cause;
|
|
1000
|
+
if (cause instanceof Error && cause.message.length > 0) {
|
|
1001
|
+
return cause.message;
|
|
1002
|
+
}
|
|
1003
|
+
switch (e._tag) {
|
|
1004
|
+
case "ValidationError": {
|
|
1005
|
+
const v = e;
|
|
1006
|
+
return `Invalid ${v.field}: ${v.reason}`;
|
|
1007
|
+
}
|
|
1008
|
+
case "NotFoundError": {
|
|
1009
|
+
const n = e;
|
|
1010
|
+
return `${n.resource} not found: ${n.id}`;
|
|
1011
|
+
}
|
|
1012
|
+
case "PermissionError": {
|
|
1013
|
+
const p = e;
|
|
1014
|
+
return `Permission denied: ${p.action} on ${p.resource}`;
|
|
1015
|
+
}
|
|
1016
|
+
case "DBError": {
|
|
1017
|
+
const d = e;
|
|
1018
|
+
return `${d.db} ${d.op} failed`;
|
|
1019
|
+
}
|
|
1020
|
+
case "FSError": {
|
|
1021
|
+
const f = e;
|
|
1022
|
+
return `${f.op} ${f.path} failed`;
|
|
1023
|
+
}
|
|
1024
|
+
case "WSError": {
|
|
1025
|
+
const w = e;
|
|
1026
|
+
return `${w.proto} ${w.kind} failed`;
|
|
1027
|
+
}
|
|
1028
|
+
case "AgentError": {
|
|
1029
|
+
const a = e;
|
|
1030
|
+
return `${a.provider} ${a.kind} failed`;
|
|
1031
|
+
}
|
|
1032
|
+
default:
|
|
1033
|
+
return e._tag;
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
var errorToResponse = (cause) => {
|
|
1037
|
+
const failure = Cause_exports.failureOption(cause);
|
|
1038
|
+
if (Option_exports.isSome(failure)) {
|
|
1039
|
+
const e = failure.value;
|
|
1040
|
+
const status = errorToStatus(e);
|
|
1041
|
+
return new Response(
|
|
1042
|
+
JSON.stringify({ error: extractErrorMessage(e), tag: e._tag }),
|
|
1043
|
+
{
|
|
1044
|
+
status,
|
|
1045
|
+
headers: { "content-type": "application/json" }
|
|
1046
|
+
}
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
console.error("[handler] uncaught defect:\n" + Cause_exports.pretty(cause));
|
|
1050
|
+
return new Response(
|
|
1051
|
+
JSON.stringify({ error: "Internal Server Error", tag: "InternalError" }),
|
|
1052
|
+
{
|
|
1053
|
+
status: 500,
|
|
1054
|
+
headers: { "content-type": "application/json" }
|
|
1055
|
+
}
|
|
1056
|
+
);
|
|
1057
|
+
};
|
|
1058
|
+
var handler = (fn) => async (req) => {
|
|
1059
|
+
const exit = await AppRuntime.runPromiseExit(fn(req));
|
|
1060
|
+
return Exit_exports.match(exit, {
|
|
1061
|
+
onFailure: (cause) => errorToResponse(cause),
|
|
1062
|
+
onSuccess: (res) => res
|
|
1063
|
+
});
|
|
1064
|
+
};
|
|
1065
|
+
var parseJsonRaw = (req) => Effect_exports.tryPromise({
|
|
1066
|
+
try: () => req.json(),
|
|
1067
|
+
catch: () => new ValidationError({
|
|
1068
|
+
field: "body",
|
|
1069
|
+
reason: "invalid JSON"
|
|
1070
|
+
})
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
// packages/feature/console/src/server/api/terminal/bubble-order.ts
|
|
1074
|
+
async function readBubbleTitles(cwd, tabId) {
|
|
1075
|
+
try {
|
|
1076
|
+
const raw = await readJsonFile(getBubbleOrderPath(cwd, tabId), []);
|
|
1077
|
+
return normalise(raw).titles;
|
|
1078
|
+
} catch {
|
|
1079
|
+
return {};
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
function normalise(raw) {
|
|
1083
|
+
if (Array.isArray(raw)) return { order: raw, titles: {} };
|
|
1084
|
+
if (raw && typeof raw === "object") {
|
|
1085
|
+
const r = raw;
|
|
1086
|
+
return {
|
|
1087
|
+
order: Array.isArray(r.order) ? r.order : [],
|
|
1088
|
+
titles: r.titles && typeof r.titles === "object" ? r.titles : {}
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
return { order: [], titles: {} };
|
|
1092
|
+
}
|
|
1093
|
+
var GET = handler(
|
|
1094
|
+
(req) => Effect_exports.gen(function* () {
|
|
1095
|
+
const sp = new URL(req.url).searchParams;
|
|
1096
|
+
const cwd = sp.get("cwd");
|
|
1097
|
+
const tabId = sp.get("tabId");
|
|
1098
|
+
if (!cwd || !tabId) {
|
|
1099
|
+
return yield* Effect_exports.fail(
|
|
1100
|
+
new ValidationError({
|
|
1101
|
+
field: !cwd ? "cwd" : "tabId",
|
|
1102
|
+
reason: "missing"
|
|
1103
|
+
})
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
const orderPath = getBubbleOrderPath(cwd, tabId);
|
|
1107
|
+
const raw = yield* Effect_exports.tryPromise({
|
|
1108
|
+
try: () => readJsonFile(orderPath, []),
|
|
1109
|
+
catch: (cause) => new FSError({ path: orderPath, op: "read", cause })
|
|
1110
|
+
});
|
|
1111
|
+
const file = normalise(raw);
|
|
1112
|
+
return ok({ order: file.order, titles: file.titles });
|
|
1113
|
+
})
|
|
1114
|
+
);
|
|
1115
|
+
var POST = handler(
|
|
1116
|
+
(req) => Effect_exports.gen(function* () {
|
|
1117
|
+
const body = yield* parseJsonRaw(req);
|
|
1118
|
+
if (!body.cwd || !body.tabId) {
|
|
1119
|
+
return yield* Effect_exports.fail(
|
|
1120
|
+
new ValidationError({
|
|
1121
|
+
field: !body.cwd ? "cwd" : "tabId",
|
|
1122
|
+
reason: "missing"
|
|
1123
|
+
})
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
const hasOrder = Array.isArray(body.order);
|
|
1127
|
+
const hasTitles = body.titles && typeof body.titles === "object";
|
|
1128
|
+
if (!hasOrder && !hasTitles) {
|
|
1129
|
+
return yield* Effect_exports.fail(
|
|
1130
|
+
new ValidationError({ field: "order|titles", reason: "missing \u2014 provide at least one" })
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
const orderPath = getBubbleOrderPath(body.cwd, body.tabId);
|
|
1134
|
+
const existing = normalise(
|
|
1135
|
+
yield* Effect_exports.tryPromise({
|
|
1136
|
+
try: () => readJsonFile(orderPath, []),
|
|
1137
|
+
catch: (cause) => new FSError({ path: orderPath, op: "read", cause })
|
|
1138
|
+
})
|
|
1139
|
+
);
|
|
1140
|
+
const next = {
|
|
1141
|
+
order: hasOrder ? body.order : existing.order,
|
|
1142
|
+
titles: hasTitles ? mergeTitles(existing.titles, body.titles) : existing.titles
|
|
1143
|
+
};
|
|
1144
|
+
yield* Effect_exports.tryPromise({
|
|
1145
|
+
try: () => writeJsonFile(orderPath, next),
|
|
1146
|
+
catch: (cause) => new FSError({ path: orderPath, op: "write", cause })
|
|
1147
|
+
});
|
|
1148
|
+
return ok({ success: true });
|
|
1149
|
+
})
|
|
1150
|
+
);
|
|
1151
|
+
function mergeTitles(base, patch) {
|
|
1152
|
+
const out = { ...base };
|
|
1153
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
1154
|
+
if (typeof v !== "string") continue;
|
|
1155
|
+
if (v === "") delete out[k];
|
|
1156
|
+
else out[k] = v.slice(0, 256);
|
|
1157
|
+
}
|
|
1158
|
+
return out;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
929
1161
|
export {
|
|
930
1162
|
registerBrowser,
|
|
931
1163
|
unregisterBrowser,
|
|
@@ -935,6 +1167,8 @@ export {
|
|
|
935
1167
|
createPendingRequest,
|
|
936
1168
|
resolvePendingRequest,
|
|
937
1169
|
sendCommandToBrowser,
|
|
1170
|
+
recordCommandResolved,
|
|
1171
|
+
getBrowserHealth,
|
|
938
1172
|
kernelManager,
|
|
939
1173
|
registerTerminal,
|
|
940
1174
|
finalizeTerminal,
|
|
@@ -965,5 +1199,6 @@ export {
|
|
|
965
1199
|
readTail,
|
|
966
1200
|
readHead,
|
|
967
1201
|
readAround,
|
|
968
|
-
grepOutput
|
|
1202
|
+
grepOutput,
|
|
1203
|
+
readBubbleTitles
|
|
969
1204
|
};
|