vibe-coding-master 0.3.4 → 0.3.6
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/backend/adapters/filesystem.js +103 -29
- package/dist/backend/api/diagnostics-routes.js +5 -0
- package/dist/backend/api/translation-routes.js +5 -0
- package/dist/backend/gateway/channels/weixin-ilink-channel.js +24 -5
- package/dist/backend/gateway/gateway-service.js +37 -8
- package/dist/backend/server.js +15 -4
- package/dist/backend/services/diagnostics-service.js +74 -0
- package/dist/backend/services/project-service.js +0 -3
- package/dist/backend/services/translation-service.js +19 -0
- package/dist/shared/types/diagnostics.js +1 -0
- package/dist-frontend/assets/{index-OPkFuHRf.js → index-CFp1kZpa.js} +5 -5
- package/dist-frontend/index.html +1 -1
- package/package.json +1 -1
|
@@ -1,58 +1,70 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export function createNodeFileSystemAdapter() {
|
|
4
|
+
const runFileOperation = createFileOperationRunner();
|
|
4
5
|
return {
|
|
5
6
|
async pathExists(targetPath) {
|
|
6
7
|
try {
|
|
7
|
-
await fs.access(targetPath);
|
|
8
|
+
await runFileOperation(() => fs.access(targetPath));
|
|
8
9
|
return true;
|
|
9
10
|
}
|
|
10
|
-
catch {
|
|
11
|
-
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (isMissingPathError(error)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
throw error;
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
async ensureDir(targetPath) {
|
|
15
|
-
await fs.mkdir(targetPath, { recursive: true });
|
|
19
|
+
await runFileOperation(() => fs.mkdir(targetPath, { recursive: true }));
|
|
16
20
|
},
|
|
17
21
|
async readDir(targetPath) {
|
|
18
|
-
return fs.readdir(targetPath);
|
|
22
|
+
return runFileOperation(() => fs.readdir(targetPath));
|
|
19
23
|
},
|
|
20
24
|
async readText(targetPath) {
|
|
21
|
-
return fs.readFile(targetPath, "utf8");
|
|
25
|
+
return runFileOperation(() => fs.readFile(targetPath, "utf8"));
|
|
22
26
|
},
|
|
23
27
|
async readTextTail(targetPath, maxBytes) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
return runFileOperation(async () => {
|
|
29
|
+
const handle = await fs.open(targetPath, "r");
|
|
30
|
+
try {
|
|
31
|
+
const stat = await handle.stat();
|
|
32
|
+
const length = Math.min(Math.max(0, Math.floor(maxBytes)), stat.size);
|
|
33
|
+
const position = stat.size - length;
|
|
34
|
+
const buffer = Buffer.alloc(length);
|
|
35
|
+
await handle.read(buffer, 0, length, position);
|
|
36
|
+
return buffer.toString("utf8");
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await handle.close();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
36
42
|
},
|
|
37
43
|
async writeText(targetPath, content) {
|
|
38
|
-
await
|
|
39
|
-
|
|
44
|
+
await runFileOperation(async () => {
|
|
45
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
46
|
+
await fs.writeFile(targetPath, content, "utf8");
|
|
47
|
+
});
|
|
40
48
|
},
|
|
41
49
|
async appendText(targetPath, content) {
|
|
42
|
-
await
|
|
43
|
-
|
|
50
|
+
await runFileOperation(async () => {
|
|
51
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
52
|
+
await fs.appendFile(targetPath, content, "utf8");
|
|
53
|
+
});
|
|
44
54
|
},
|
|
45
55
|
async readJson(targetPath) {
|
|
46
|
-
return JSON.parse(await fs.readFile(targetPath, "utf8"));
|
|
56
|
+
return JSON.parse(await runFileOperation(() => fs.readFile(targetPath, "utf8")));
|
|
47
57
|
},
|
|
48
58
|
async writeJson(targetPath, value) {
|
|
49
59
|
await this.writeText(targetPath, `${JSON.stringify(value, null, 2)}\n`);
|
|
50
60
|
},
|
|
51
61
|
async writeJsonAtomic(targetPath, value) {
|
|
52
|
-
await
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
await runFileOperation(async () => {
|
|
63
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
64
|
+
const tempPath = `${targetPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
65
|
+
await fs.writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
66
|
+
await fs.rename(tempPath, targetPath);
|
|
67
|
+
});
|
|
56
68
|
},
|
|
57
69
|
async ensureFile(targetPath, content, options = {}) {
|
|
58
70
|
if (!options.overwrite && await this.pathExists(targetPath)) {
|
|
@@ -62,13 +74,75 @@ export function createNodeFileSystemAdapter() {
|
|
|
62
74
|
return true;
|
|
63
75
|
},
|
|
64
76
|
async removePath(targetPath, options = {}) {
|
|
65
|
-
await fs.rm(targetPath, {
|
|
77
|
+
await runFileOperation(() => fs.rm(targetPath, {
|
|
66
78
|
recursive: options.recursive ?? false,
|
|
67
79
|
force: options.force ?? false
|
|
68
|
-
});
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const DEFAULT_FILE_OPERATION_CONCURRENCY = 32;
|
|
85
|
+
const OPEN_FILE_RETRY_DELAYS_MS = [25, 50, 100, 200, 400, 800];
|
|
86
|
+
function createFileOperationRunner(maxConcurrent = DEFAULT_FILE_OPERATION_CONCURRENCY) {
|
|
87
|
+
let active = 0;
|
|
88
|
+
const waiters = [];
|
|
89
|
+
async function acquire() {
|
|
90
|
+
if (active < maxConcurrent) {
|
|
91
|
+
active += 1;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await new Promise((resolve) => waiters.push(resolve));
|
|
95
|
+
}
|
|
96
|
+
function release() {
|
|
97
|
+
const next = waiters.shift();
|
|
98
|
+
if (next) {
|
|
99
|
+
next();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
active = Math.max(0, active - 1);
|
|
103
|
+
}
|
|
104
|
+
return async (operation) => {
|
|
105
|
+
await acquire();
|
|
106
|
+
try {
|
|
107
|
+
return await retryOpenFileLimit(operation);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
release();
|
|
69
111
|
}
|
|
70
112
|
};
|
|
71
113
|
}
|
|
114
|
+
async function retryOpenFileLimit(operation) {
|
|
115
|
+
let attempt = 0;
|
|
116
|
+
for (;;) {
|
|
117
|
+
try {
|
|
118
|
+
return await operation();
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const delayMs = OPEN_FILE_RETRY_DELAYS_MS[attempt];
|
|
122
|
+
if (!isOpenFileLimitError(error) || delayMs === undefined) {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
attempt += 1;
|
|
126
|
+
await delay(delayMs);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function isOpenFileLimitError(error) {
|
|
131
|
+
const code = getErrorCode(error);
|
|
132
|
+
return code === "EMFILE" || code === "ENFILE";
|
|
133
|
+
}
|
|
134
|
+
function isMissingPathError(error) {
|
|
135
|
+
const code = getErrorCode(error);
|
|
136
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
137
|
+
}
|
|
138
|
+
function getErrorCode(error) {
|
|
139
|
+
return typeof error === "object" && error !== null && "code" in error
|
|
140
|
+
? String(error.code)
|
|
141
|
+
: undefined;
|
|
142
|
+
}
|
|
143
|
+
function delay(ms) {
|
|
144
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
145
|
+
}
|
|
72
146
|
export function resolveRepoPath(repoRoot, repoRelativePath) {
|
|
73
147
|
if (path.isAbsolute(repoRelativePath)) {
|
|
74
148
|
return repoRelativePath;
|
|
@@ -60,6 +60,11 @@ export function registerTranslationRoutes(app, deps) {
|
|
|
60
60
|
await deps.translationService.clearSession(request.params.sessionId);
|
|
61
61
|
return { ok: true };
|
|
62
62
|
});
|
|
63
|
+
app.post("/api/translation/sessions/:sessionId/stop", async (request) => {
|
|
64
|
+
await requireCurrentProject(deps.projectService);
|
|
65
|
+
await deps.translationService.stopSession(request.params.sessionId);
|
|
66
|
+
return { ok: true };
|
|
67
|
+
});
|
|
63
68
|
app.post("/api/translation/sessions/:sessionId/retry/:translationId", async (request) => {
|
|
64
69
|
await requireCurrentProject(deps.projectService);
|
|
65
70
|
return deps.translationService.retryTranslation(request.params.sessionId, request.params.translationId);
|
|
@@ -47,14 +47,14 @@ export function createWeixinIlinkChannel(options = {}) {
|
|
|
47
47
|
async function fetchJson(input) {
|
|
48
48
|
const controller = input.timeoutMs !== undefined ? new AbortController() : undefined;
|
|
49
49
|
const timeout = controller ? setTimeout(() => controller.abort(), input.timeoutMs) : undefined;
|
|
50
|
-
const
|
|
50
|
+
const combinedSignal = combineSignals(controller?.signal, input.signal);
|
|
51
51
|
const url = new URL(input.endpoint, ensureTrailingSlash(input.requestBaseUrl));
|
|
52
52
|
try {
|
|
53
53
|
const response = await fetchImpl(url, {
|
|
54
54
|
method: input.method,
|
|
55
55
|
headers: input.jsonHeaders === false ? buildIlinkAppHeaders() : buildJsonHeaders(input.token),
|
|
56
56
|
body: input.body === undefined ? undefined : JSON.stringify(input.body),
|
|
57
|
-
signal
|
|
57
|
+
signal: combinedSignal.signal
|
|
58
58
|
});
|
|
59
59
|
const rawText = await response.text();
|
|
60
60
|
if (!response.ok) {
|
|
@@ -66,6 +66,7 @@ export function createWeixinIlinkChannel(options = {}) {
|
|
|
66
66
|
if (timeout) {
|
|
67
67
|
clearTimeout(timeout);
|
|
68
68
|
}
|
|
69
|
+
combinedSignal.cleanup();
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
async function post(input) {
|
|
@@ -285,10 +286,22 @@ function randomWechatUin() {
|
|
|
285
286
|
}
|
|
286
287
|
function combineSignals(first, second) {
|
|
287
288
|
if (!first) {
|
|
288
|
-
return
|
|
289
|
+
return {
|
|
290
|
+
signal: second,
|
|
291
|
+
cleanup() { }
|
|
292
|
+
};
|
|
289
293
|
}
|
|
290
294
|
if (!second) {
|
|
291
|
-
return
|
|
295
|
+
return {
|
|
296
|
+
signal: first,
|
|
297
|
+
cleanup() { }
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (first === second) {
|
|
301
|
+
return {
|
|
302
|
+
signal: first,
|
|
303
|
+
cleanup() { }
|
|
304
|
+
};
|
|
292
305
|
}
|
|
293
306
|
const controller = new AbortController();
|
|
294
307
|
const abort = () => controller.abort();
|
|
@@ -297,7 +310,13 @@ function combineSignals(first, second) {
|
|
|
297
310
|
if (first.aborted || second.aborted) {
|
|
298
311
|
controller.abort();
|
|
299
312
|
}
|
|
300
|
-
return
|
|
313
|
+
return {
|
|
314
|
+
signal: controller.signal,
|
|
315
|
+
cleanup() {
|
|
316
|
+
first.removeEventListener("abort", abort);
|
|
317
|
+
second.removeEventListener("abort", abort);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
301
320
|
}
|
|
302
321
|
function stringOrUndefined(value) {
|
|
303
322
|
return typeof value === "string" ? value : undefined;
|
|
@@ -25,28 +25,52 @@ export function createGatewayService(deps) {
|
|
|
25
25
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
26
26
|
let pollAbort = null;
|
|
27
27
|
let pollLoopPromise = null;
|
|
28
|
+
let pollStartingPromise = null;
|
|
28
29
|
let qrLogin = null;
|
|
29
30
|
let lastFailedTranslation = null;
|
|
30
31
|
function isRunning() {
|
|
31
32
|
return Boolean(pollAbort && !pollAbort.signal.aborted);
|
|
32
33
|
}
|
|
33
34
|
async function ensurePolling() {
|
|
34
|
-
|
|
35
|
-
if (!settings.binding.token || isRunning()) {
|
|
35
|
+
if (isRunning()) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (pollStartingPromise) {
|
|
39
|
+
await pollStartingPromise;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
pollStartingPromise = (async () => {
|
|
43
|
+
const settings = await deps.settings.loadSettings();
|
|
44
|
+
if (!settings.binding.token || isRunning()) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const controller = new AbortController();
|
|
48
|
+
pollAbort = controller;
|
|
49
|
+
const loop = pollLoop(controller.signal).finally(() => {
|
|
50
|
+
if (pollAbort === controller) {
|
|
51
|
+
pollAbort = null;
|
|
52
|
+
}
|
|
53
|
+
if (pollLoopPromise === loop) {
|
|
54
|
+
pollLoopPromise = null;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
pollLoopPromise = loop;
|
|
58
|
+
})().finally(() => {
|
|
59
|
+
pollStartingPromise = null;
|
|
42
60
|
});
|
|
61
|
+
await pollStartingPromise;
|
|
43
62
|
}
|
|
44
63
|
async function stopPolling() {
|
|
64
|
+
await pollStartingPromise?.catch(() => undefined);
|
|
45
65
|
if (!pollAbort) {
|
|
46
66
|
return;
|
|
47
67
|
}
|
|
48
|
-
pollAbort
|
|
68
|
+
const controller = pollAbort;
|
|
69
|
+
controller.abort();
|
|
49
70
|
await pollLoopPromise?.catch(() => undefined);
|
|
71
|
+
if (pollAbort === controller) {
|
|
72
|
+
pollAbort = null;
|
|
73
|
+
}
|
|
50
74
|
}
|
|
51
75
|
function toAccount(settings) {
|
|
52
76
|
if (!settings.binding.token) {
|
|
@@ -643,7 +667,7 @@ export function createGatewayService(deps) {
|
|
|
643
667
|
await ensurePolling();
|
|
644
668
|
},
|
|
645
669
|
stop() {
|
|
646
|
-
|
|
670
|
+
return stopPolling();
|
|
647
671
|
},
|
|
648
672
|
async getStatus() {
|
|
649
673
|
const settings = await syncDesktopContext(await deps.settings.loadSettings());
|
|
@@ -802,6 +826,11 @@ export function createGatewayService(deps) {
|
|
|
802
826
|
preview: output.text,
|
|
803
827
|
error: output.translationError
|
|
804
828
|
});
|
|
829
|
+
},
|
|
830
|
+
getDiagnostics() {
|
|
831
|
+
return {
|
|
832
|
+
polling: isRunning()
|
|
833
|
+
};
|
|
805
834
|
}
|
|
806
835
|
};
|
|
807
836
|
function getLatestPmReply(settings) {
|
package/dist/backend/server.js
CHANGED
|
@@ -17,6 +17,7 @@ import { createNodeFileSystemAdapter } from "./adapters/filesystem.js";
|
|
|
17
17
|
import { createNodePtyTerminalRuntime } from "./runtime/node-pty-runtime.js";
|
|
18
18
|
import { createOpenAiCompatibleTranslationProvider } from "./adapters/translation-provider.js";
|
|
19
19
|
import { registerGatewayRoutes } from "./api/gateway-routes.js";
|
|
20
|
+
import { registerDiagnosticsRoutes } from "./api/diagnostics-routes.js";
|
|
20
21
|
import { createWeixinIlinkChannel } from "./gateway/channels/weixin-ilink-channel.js";
|
|
21
22
|
import { createGatewayAuditLog } from "./gateway/gateway-audit-log.js";
|
|
22
23
|
import { createGatewayService } from "./gateway/gateway-service.js";
|
|
@@ -30,6 +31,7 @@ import { createRoundService } from "./services/round-service.js";
|
|
|
30
31
|
import { createStatusService } from "./services/status-service.js";
|
|
31
32
|
import { createTaskService } from "./services/task-service.js";
|
|
32
33
|
import { createTranslationService } from "./services/translation-service.js";
|
|
34
|
+
import { createDiagnosticsService } from "./services/diagnostics-service.js";
|
|
33
35
|
import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
|
|
34
36
|
import { registerArtifactRoutes } from "./api/artifact-routes.js";
|
|
35
37
|
import { registerClaudeHookRoutes } from "./api/claude-hook-routes.js";
|
|
@@ -54,10 +56,12 @@ export async function createServer(deps, options = {}) {
|
|
|
54
56
|
error: {
|
|
55
57
|
code: vcmError.code,
|
|
56
58
|
message: vcmError.message,
|
|
57
|
-
hint: vcmError.hint
|
|
59
|
+
hint: vcmError.hint,
|
|
60
|
+
runtime: deps.diagnosticsService.getErrorRuntimeInfo()
|
|
58
61
|
}
|
|
59
62
|
});
|
|
60
63
|
});
|
|
64
|
+
registerDiagnosticsRoutes(app, { diagnosticsService: deps.diagnosticsService });
|
|
61
65
|
registerAppSettingsRoutes(app, { appSettings: deps.appSettings });
|
|
62
66
|
registerClaudeHookRoutes(app, { claudeHookService: deps.claudeHookService });
|
|
63
67
|
registerCodexHookRoutes(app, { codexHookService: deps.codexHookService });
|
|
@@ -111,7 +115,7 @@ export async function createServer(deps, options = {}) {
|
|
|
111
115
|
await deps.gatewayService.start();
|
|
112
116
|
});
|
|
113
117
|
app.addHook("onClose", async () => {
|
|
114
|
-
deps.gatewayService.stop();
|
|
118
|
+
await deps.gatewayService.stop();
|
|
115
119
|
});
|
|
116
120
|
if (options.staticDir) {
|
|
117
121
|
await app.register(fastifyStatic, {
|
|
@@ -148,7 +152,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
148
152
|
const runtime = createNodePtyTerminalRuntime({ fs });
|
|
149
153
|
const registry = createSessionRegistry();
|
|
150
154
|
const artifactService = createArtifactService(fs);
|
|
151
|
-
const projectService = createProjectService({ fs, git,
|
|
155
|
+
const projectService = createProjectService({ fs, git, appSettings });
|
|
152
156
|
const harnessService = createHarnessService({
|
|
153
157
|
fs,
|
|
154
158
|
runtime,
|
|
@@ -248,6 +252,12 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
248
252
|
sessionService,
|
|
249
253
|
roundService
|
|
250
254
|
});
|
|
255
|
+
const diagnosticsService = createDiagnosticsService({
|
|
256
|
+
appRoot: getAppRoot(),
|
|
257
|
+
runtime,
|
|
258
|
+
gatewayService,
|
|
259
|
+
translationService
|
|
260
|
+
});
|
|
251
261
|
return {
|
|
252
262
|
appSettings,
|
|
253
263
|
projectService,
|
|
@@ -264,7 +274,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
264
274
|
statusService,
|
|
265
275
|
translationService,
|
|
266
276
|
gatewayService,
|
|
267
|
-
runtime
|
|
277
|
+
runtime,
|
|
278
|
+
diagnosticsService
|
|
268
279
|
};
|
|
269
280
|
}
|
|
270
281
|
export function getDefaultStaticDir() {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export function createDiagnosticsService(deps) {
|
|
5
|
+
const version = readPackageVersion(deps.appRoot);
|
|
6
|
+
return {
|
|
7
|
+
async getRuntimeDiagnostics() {
|
|
8
|
+
const runtimeSessions = deps.runtime.listSessions();
|
|
9
|
+
return {
|
|
10
|
+
version,
|
|
11
|
+
pid: process.pid,
|
|
12
|
+
cwd: process.cwd(),
|
|
13
|
+
execPath: process.execPath,
|
|
14
|
+
nodeVersion: process.version,
|
|
15
|
+
platform: process.platform,
|
|
16
|
+
arch: process.arch,
|
|
17
|
+
uptimeSeconds: Math.round(process.uptime()),
|
|
18
|
+
fdCount: await readFdCount(),
|
|
19
|
+
openFilesLimit: await readOpenFilesLimit(),
|
|
20
|
+
runtimeSessions: {
|
|
21
|
+
total: runtimeSessions.length,
|
|
22
|
+
running: runtimeSessions.filter((session) => session.status === "running").length
|
|
23
|
+
},
|
|
24
|
+
gateway: deps.gatewayService.getDiagnostics(),
|
|
25
|
+
translation: deps.translationService.getDiagnostics()
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
getErrorRuntimeInfo() {
|
|
29
|
+
return {
|
|
30
|
+
version,
|
|
31
|
+
pid: process.pid,
|
|
32
|
+
cwd: process.cwd()
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function readPackageVersion(appRoot) {
|
|
38
|
+
const packageJsonPath = path.join(appRoot, "package.json");
|
|
39
|
+
try {
|
|
40
|
+
const raw = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
41
|
+
return typeof raw.version === "string" && raw.version.trim() ? raw.version : "unknown";
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return process.env.npm_package_version || "unknown";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function readFdCount() {
|
|
48
|
+
try {
|
|
49
|
+
return (await fs.readdir("/proc/self/fd")).length;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function readOpenFilesLimit() {
|
|
56
|
+
try {
|
|
57
|
+
const limits = await fs.readFile("/proc/self/limits", "utf8");
|
|
58
|
+
const line = limits.split("\n").find((candidate) => candidate.startsWith("Max open files"));
|
|
59
|
+
if (!line) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const match = line.match(/^Max open files\s+(\S+)\s+(\S+)/);
|
|
63
|
+
if (!match) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
soft: match[1] ?? "unknown",
|
|
68
|
+
hard: match[2] ?? "unknown"
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -139,9 +139,6 @@ export function createProjectService(deps) {
|
|
|
139
139
|
warnings.push(`Unable to read Git ahead/behind status. ${getErrorHint(caught)}`);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
if (!(await deps.claude.isAvailable(config.claudeCommand))) {
|
|
143
|
-
warnings.push("Claude Code command is not available. You can still inspect artifacts, but sessions will not start.");
|
|
144
|
-
}
|
|
145
142
|
const pullDisabledReason = getPullDisabledReason({ isDirty, upstreamBranch });
|
|
146
143
|
return {
|
|
147
144
|
repoRoot,
|
|
@@ -703,6 +703,10 @@ export function createTranslationService(deps) {
|
|
|
703
703
|
});
|
|
704
704
|
return () => {
|
|
705
705
|
state.listeners.delete(listener);
|
|
706
|
+
if (state.listeners.size === 0 && state.unsubscribeTranscript) {
|
|
707
|
+
state.unsubscribeTranscript();
|
|
708
|
+
state.unsubscribeTranscript = undefined;
|
|
709
|
+
}
|
|
706
710
|
};
|
|
707
711
|
},
|
|
708
712
|
async clearSession(sessionId) {
|
|
@@ -795,6 +799,21 @@ export function createTranslationService(deps) {
|
|
|
795
799
|
userPrompt: prompt.userPrompt
|
|
796
800
|
});
|
|
797
801
|
return result.text.trim();
|
|
802
|
+
},
|
|
803
|
+
getDiagnostics() {
|
|
804
|
+
let transcriptWatchers = 0;
|
|
805
|
+
let listeners = 0;
|
|
806
|
+
for (const state of sessionStates.values()) {
|
|
807
|
+
if (state.unsubscribeTranscript) {
|
|
808
|
+
transcriptWatchers += 1;
|
|
809
|
+
}
|
|
810
|
+
listeners += state.listeners.size;
|
|
811
|
+
}
|
|
812
|
+
return {
|
|
813
|
+
sessions: sessionStates.size,
|
|
814
|
+
transcriptWatchers,
|
|
815
|
+
listeners
|
|
816
|
+
};
|
|
798
817
|
}
|
|
799
818
|
};
|
|
800
819
|
async function writeToCurrentRole(repoRoot, taskSlug, role, text) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|