claude-code-kanban 4.28.0 → 4.29.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/lib/priority.js +45 -0
- package/lib/terminal.js +44 -0
- package/package.json +1 -1
package/lib/priority.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Windows boosts the threads of the foreground window, and a process behind a hidden ConPTY never
|
|
4
|
+
// gets that, so the one the user types into competes on equal terms with every busy agent.
|
|
5
|
+
// Elsewhere a raise needs root, and a raised nice value would pass on to every child, so this is
|
|
6
|
+
// Windows only. There a child of an above-normal process starts at normal, so the tools it runs
|
|
7
|
+
// stay unboosted.
|
|
8
|
+
const { execFile } = require('node:child_process');
|
|
9
|
+
const os = require('node:os');
|
|
10
|
+
|
|
11
|
+
const { PRIORITY_ABOVE_NORMAL } = os.constants.priority;
|
|
12
|
+
|
|
13
|
+
function enabled(platform, env) {
|
|
14
|
+
return platform === 'win32' && env.CCK_PRIORITY_BOOST !== '0';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function raise(pid = 0, platform = process.platform, env = process.env) {
|
|
18
|
+
if (!enabled(platform, env)) return null;
|
|
19
|
+
try {
|
|
20
|
+
const before = os.getPriority(pid);
|
|
21
|
+
if (before <= PRIORITY_ABOVE_NORMAL) return null;
|
|
22
|
+
os.setPriority(pid, PRIORITY_ABOVE_NORMAL);
|
|
23
|
+
return before;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function restore(pid, before) {
|
|
30
|
+
try { os.setPriority(pid, before); } catch { /* the process is gone */ }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ConPTY's console host relays every byte between cck and the shell, and node-pty does not expose
|
|
34
|
+
// its pid. Under load a normal-priority host starves for seconds, until Windows' anti-starvation
|
|
35
|
+
// boost picks it up.
|
|
36
|
+
function raiseConsoleHosts(done) {
|
|
37
|
+
if (!enabled(process.platform, process.env)) return done?.();
|
|
38
|
+
const query = `(Get-CimInstance Win32_Process -Filter "ParentProcessId=${process.pid} AND (Name='conhost.exe' OR Name='OpenConsole.exe')").ProcessId`;
|
|
39
|
+
execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', query], { windowsHide: true, timeout: 30000 }, (err, stdout) => {
|
|
40
|
+
if (!err) for (const pid of stdout.split(/\s+/).filter(Boolean)) raise(Number(pid));
|
|
41
|
+
done?.();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { raise, restore, raiseConsoleHosts };
|
package/lib/terminal.js
CHANGED
|
@@ -13,6 +13,7 @@ const fs = require('node:fs');
|
|
|
13
13
|
const os = require('node:os');
|
|
14
14
|
const path = require('node:path');
|
|
15
15
|
const { isContained } = require('./contain');
|
|
16
|
+
const priority = require('./priority');
|
|
16
17
|
|
|
17
18
|
const WS_PATH = '/api/terminal/ws';
|
|
18
19
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -32,6 +33,7 @@ const PROMPT_QUIET_MS = 400;
|
|
|
32
33
|
const HIGH_WATER = 128 * 1024;
|
|
33
34
|
const LOW_WATER = 16 * 1024;
|
|
34
35
|
const HELLO_TIMEOUT_MS = 5000;
|
|
36
|
+
const BOOST_TRIES = 30;
|
|
35
37
|
const MAX_PAYLOAD = 1024 * 1024;
|
|
36
38
|
// The hub hands its children PORT=0 and friends; a dev server started in the terminal
|
|
37
39
|
// would otherwise bind a random port, and a nested `claude` would think it runs inside one.
|
|
@@ -224,6 +226,7 @@ function refuse(socket, status, reason) {
|
|
|
224
226
|
*/
|
|
225
227
|
function createTerminalService(o) {
|
|
226
228
|
const { config, net } = o;
|
|
229
|
+
if (config.enabled) priority.raise();
|
|
227
230
|
const token = o.token || crypto.randomBytes(32).toString('hex');
|
|
228
231
|
const sessions = new Map();
|
|
229
232
|
let pty = null;
|
|
@@ -302,6 +305,43 @@ function createTerminalService(o) {
|
|
|
302
305
|
};
|
|
303
306
|
}
|
|
304
307
|
|
|
308
|
+
// The process a viewer types into: claude, found through its registry entry, or the shell.
|
|
309
|
+
function inputPid(s) {
|
|
310
|
+
if (s.mode === 'shell') return s.pty.pid;
|
|
311
|
+
if (s.claudePid || s.mode === 'pick') return s.claudePid;
|
|
312
|
+
const live = o.liveSessions();
|
|
313
|
+
const own = live.find((l) => l.pid && l.sessionId === s.id && l.startedAt >= s.startedAt - PICK_START_SLACK_MS);
|
|
314
|
+
if (own || s.mode !== 'fork') return own?.pid;
|
|
315
|
+
const claimed = new Set([...sessions.values()].map((x) => x.claudePid).filter(Boolean));
|
|
316
|
+
return findPickProcess(live, s, claimed);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// claude writes its registry entry a moment after it starts, so the pid is polled for.
|
|
320
|
+
function syncBoost(s) {
|
|
321
|
+
const want = s.sockets.size > 0 && !s.exited;
|
|
322
|
+
if (want && !s.boost) {
|
|
323
|
+
if (s.boostTimer) return;
|
|
324
|
+
let tries = 0;
|
|
325
|
+
const tryBoost = () => {
|
|
326
|
+
const pid = inputPid(s);
|
|
327
|
+
if (!pid && ++tries < BOOST_TRIES) return;
|
|
328
|
+
clearInterval(s.boostTimer);
|
|
329
|
+
s.boostTimer = null;
|
|
330
|
+
if (!pid) return;
|
|
331
|
+
if (s.mode !== 'shell') s.claudePid = pid;
|
|
332
|
+
const before = priority.raise(pid);
|
|
333
|
+
s.boost = { pid, before };
|
|
334
|
+
};
|
|
335
|
+
s.boostTimer = setInterval(tryBoost, PICK_POLL_MS);
|
|
336
|
+
tryBoost();
|
|
337
|
+
} else if (!want) {
|
|
338
|
+
clearInterval(s.boostTimer);
|
|
339
|
+
s.boostTimer = null;
|
|
340
|
+
if (s.boost && s.boost.before !== null && !s.exited) priority.restore(s.boost.pid, s.boost.before);
|
|
341
|
+
s.boost = null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
305
345
|
function spawnSession(id, mode, cwdCandidate, cols, rows, spec, extraEnv) {
|
|
306
346
|
const cwd = cwdCandidate && fs.existsSync(cwdCandidate) ? cwdCandidate : os.homedir();
|
|
307
347
|
const env = { ...ptyEnv({ claudeDir: o.claudeDir, isDefaultDir: o.isDefaultDir, noFlicker: config.noFlicker }), ...extraEnv };
|
|
@@ -335,6 +375,7 @@ function createTerminalService(o) {
|
|
|
335
375
|
proc.onExit(({ exitCode }) => {
|
|
336
376
|
s.exited = true;
|
|
337
377
|
clearInterval(s.pickTimer);
|
|
378
|
+
clearInterval(s.boostTimer);
|
|
338
379
|
if (sessions.get(s.id) === s) sessions.delete(s.id);
|
|
339
380
|
o.onExit?.(s.id);
|
|
340
381
|
if (!s.ended) o.onChange?.();
|
|
@@ -345,6 +386,7 @@ function createTerminalService(o) {
|
|
|
345
386
|
term.dispose();
|
|
346
387
|
});
|
|
347
388
|
sessions.set(id, s);
|
|
389
|
+
priority.raiseConsoleHosts();
|
|
348
390
|
o.onChange?.();
|
|
349
391
|
return s;
|
|
350
392
|
}
|
|
@@ -389,6 +431,7 @@ function createTerminalService(o) {
|
|
|
389
431
|
function attach(ws, s, attached) {
|
|
390
432
|
ws.unacked = 0;
|
|
391
433
|
s.sockets.add(ws);
|
|
434
|
+
syncBoost(s);
|
|
392
435
|
send(ws, { t: 'ready', attached });
|
|
393
436
|
const snapshot = s.serializer.serialize();
|
|
394
437
|
if (snapshot) sendOutput(ws, Buffer.from(snapshot, 'utf8'));
|
|
@@ -411,6 +454,7 @@ function createTerminalService(o) {
|
|
|
411
454
|
});
|
|
412
455
|
ws.on('close', () => {
|
|
413
456
|
s.sockets.delete(ws);
|
|
457
|
+
syncBoost(s);
|
|
414
458
|
if (!s.exited) updateFlow(s);
|
|
415
459
|
});
|
|
416
460
|
}
|