clideck 1.31.8 → 1.31.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.31.8",
3
+ "version": "1.31.9",
4
4
  "description": "One screen for all your AI coding agents — run, monitor, and manage multiple CLI agents from a single browser tab",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -49,6 +49,15 @@ function checkSelfUpdate() {
49
49
 
50
50
  checkSelfUpdate().then(() => {
51
51
 
52
+ const { acquireServerLock, removeLockIfOwned } = require('./single-instance');
53
+ const serverLock = acquireServerLock();
54
+ if (!serverLock.ok) {
55
+ const url = serverLock.lock?.url || `http://127.0.0.1:${serverLock.lock?.port || PORT}`;
56
+ const hint = terminalLink(url);
57
+ console.log(`CliDeck is already running at ${hint}`);
58
+ process.exit(0);
59
+ }
60
+
52
61
  const { onConnection } = require('./handlers');
53
62
  const sessions = require('./sessions');
54
63
 
@@ -301,10 +310,12 @@ function onShutdown() {
301
310
  plugins.shutdown();
302
311
  activity.stop();
303
312
  sessions.shutdown(getConfig());
313
+ removeLockIfOwned();
304
314
  process.exit(0);
305
315
  }
306
316
  process.on('SIGINT', onShutdown);
307
317
  process.on('SIGTERM', onShutdown);
318
+ process.on('exit', removeLockIfOwned);
308
319
 
309
320
  server.listen(PORT, HOST, () => {
310
321
  const v = require('./package.json').version;
package/session-ask.js CHANGED
@@ -3,6 +3,8 @@ const transcript = require('./transcript');
3
3
  const MAX_BODY = 2 * 1024 * 1024;
4
4
  const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
5
5
  const MAX_TIMEOUT_MS = 60 * 60 * 1000;
6
+ const BRACKETED_PASTE_START = '\x1b[200~';
7
+ const BRACKETED_PASTE_END = '\x1b[201~';
6
8
 
7
9
  function sendJson(res, status, payload) {
8
10
  res.writeHead(status, { 'Content-Type': 'application/json' });
@@ -99,7 +101,8 @@ function submitAskInput(sessionsApi, targetId, message) {
99
101
  const sessions = sessionsApi.getSessions();
100
102
  const timers = [];
101
103
 
102
- sessionsApi.input({ id: targetId, data: message });
104
+ const payload = `\n\n${message}`;
105
+ sessionsApi.input({ id: targetId, data: `${BRACKETED_PASTE_START}${payload}${BRACKETED_PASTE_END}` });
103
106
  const delay = askSubmitDelay(message);
104
107
  timers.push(setTimeout(() => sessionsApi.input({ id: targetId, data: '\r' }), delay));
105
108
  timers.push(setTimeout(() => {
@@ -171,7 +174,9 @@ async function askSession(payload, sessionsApi) {
171
174
  if (!caller) throw jsonError('Caller session is not active', 404);
172
175
 
173
176
  const [targetId, target] = findTarget(sessions, callerId, caller, payload.target);
174
- if (target.working) throw jsonError(`Target session "${target.name}" is already working`, 409);
177
+ if (target.working) {
178
+ throw jsonError(`Target session "${target.name}" is busy. CliDeck ask only sends to idle sessions. Try again later, choose another idle session, or ask the user how to proceed.`, 409);
179
+ }
175
180
 
176
181
  const message = String(payload.message || '').trim();
177
182
  if (!message) throw jsonError('Message is required');
@@ -0,0 +1,51 @@
1
+ const { existsSync, readFileSync, writeFileSync, unlinkSync } = require('fs');
2
+ const { join } = require('path');
3
+ const { DATA_DIR } = require('./paths');
4
+ const { PORT, HOST, localUrl } = require('./runtime');
5
+
6
+ const LOCK_PATH = join(DATA_DIR, 'server.lock');
7
+
8
+ function isPidAlive(pid) {
9
+ const n = Number(pid);
10
+ if (!Number.isInteger(n) || n <= 0) return false;
11
+ try {
12
+ process.kill(n, 0);
13
+ return true;
14
+ } catch (e) {
15
+ return e.code === 'EPERM';
16
+ }
17
+ }
18
+
19
+ function readLock() {
20
+ if (!existsSync(LOCK_PATH)) return null;
21
+ try {
22
+ return JSON.parse(readFileSync(LOCK_PATH, 'utf8'));
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ function removeLockIfOwned() {
29
+ const lock = readLock();
30
+ if (!lock || lock.pid !== process.pid) return;
31
+ try { unlinkSync(LOCK_PATH); } catch {}
32
+ }
33
+
34
+ function acquireServerLock() {
35
+ const existing = readLock();
36
+ if (existing && existing.pid !== process.pid && isPidAlive(existing.pid)) {
37
+ return { ok: false, lock: existing };
38
+ }
39
+
40
+ const lock = {
41
+ pid: process.pid,
42
+ host: HOST,
43
+ port: PORT,
44
+ url: localUrl(),
45
+ startedAt: new Date().toISOString(),
46
+ };
47
+ writeFileSync(LOCK_PATH, JSON.stringify(lock, null, 2) + '\n');
48
+ return { ok: true, lock };
49
+ }
50
+
51
+ module.exports = { acquireServerLock, removeLockIfOwned, isPidAlive, LOCK_PATH };