leduo-patrol 2.0.1 → 2.2.3

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.
Files changed (188) hide show
  1. package/README.md +27 -4
  2. package/dist/server/__tests__/access-key-prompt.test.js +80 -0
  3. package/dist/server/__tests__/acp-session.test.js +92 -0
  4. package/dist/server/__tests__/activity-monitor.test.js +13 -1
  5. package/dist/server/__tests__/claude-cli-session.test.js +17 -0
  6. package/dist/server/__tests__/pty-runtime.test.js +28 -0
  7. package/dist/server/__tests__/session-manager.test.js +215 -1
  8. package/dist/server/access-key-prompt.js +84 -0
  9. package/dist/server/acp-session.js +476 -0
  10. package/dist/server/activity-monitor.js +22 -7
  11. package/dist/server/claude-cli-session.js +57 -12
  12. package/dist/server/index.js +104 -21
  13. package/dist/server/launch-mode.js +4 -22
  14. package/dist/server/pty-runtime.js +28 -0
  15. package/dist/server/session-manager.js +1117 -121
  16. package/dist/server/shell-session.js +2 -0
  17. package/dist/server/startup-preferences.js +45 -0
  18. package/dist/web/assets/index-B-YXVUoQ.css +1 -0
  19. package/dist/web/assets/index-Bu0K7QgY.js +21 -0
  20. package/dist/web/index.html +2 -2
  21. package/package.json +3 -1
  22. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/LICENSE +191 -0
  23. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/README.md +53 -0
  24. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.d.ts +823 -0
  25. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.js +965 -0
  26. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.js.map +1 -0
  27. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.test.d.ts +1 -0
  28. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.test.js +839 -0
  29. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.test.js.map +1 -0
  30. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/agent.d.ts +2 -0
  31. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js +225 -0
  32. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js.map +1 -0
  33. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/client.d.ts +2 -0
  34. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/client.js +130 -0
  35. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/client.js.map +1 -0
  36. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.d.ts +35 -0
  37. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js +5 -0
  38. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js.map +1 -0
  39. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/index.d.ts +27 -0
  40. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/index.js +28 -0
  41. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/index.js.map +1 -0
  42. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.d.ts +2870 -0
  43. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js +3 -0
  44. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js.map +1 -0
  45. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.d.ts +5333 -0
  46. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js +1554 -0
  47. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js.map +1 -0
  48. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/stream.d.ts +24 -0
  49. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/stream.js +64 -0
  50. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/stream.js.map +1 -0
  51. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/package.json +66 -0
  52. package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/schema/schema.json +4125 -0
  53. package/vendor/claude-code-acp/node_modules/@types/node/LICENSE +21 -0
  54. package/vendor/claude-code-acp/node_modules/@types/node/README.md +15 -0
  55. package/vendor/claude-code-acp/node_modules/@types/node/assert/strict.d.ts +105 -0
  56. package/vendor/claude-code-acp/node_modules/@types/node/assert.d.ts +955 -0
  57. package/vendor/claude-code-acp/node_modules/@types/node/async_hooks.d.ts +623 -0
  58. package/vendor/claude-code-acp/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  59. package/vendor/claude-code-acp/node_modules/@types/node/buffer.d.ts +1810 -0
  60. package/vendor/claude-code-acp/node_modules/@types/node/child_process.d.ts +1428 -0
  61. package/vendor/claude-code-acp/node_modules/@types/node/cluster.d.ts +486 -0
  62. package/vendor/claude-code-acp/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  63. package/vendor/claude-code-acp/node_modules/@types/node/console.d.ts +151 -0
  64. package/vendor/claude-code-acp/node_modules/@types/node/constants.d.ts +20 -0
  65. package/vendor/claude-code-acp/node_modules/@types/node/crypto.d.ts +4065 -0
  66. package/vendor/claude-code-acp/node_modules/@types/node/dgram.d.ts +564 -0
  67. package/vendor/claude-code-acp/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  68. package/vendor/claude-code-acp/node_modules/@types/node/dns/promises.d.ts +503 -0
  69. package/vendor/claude-code-acp/node_modules/@types/node/dns.d.ts +922 -0
  70. package/vendor/claude-code-acp/node_modules/@types/node/domain.d.ts +166 -0
  71. package/vendor/claude-code-acp/node_modules/@types/node/events.d.ts +1054 -0
  72. package/vendor/claude-code-acp/node_modules/@types/node/fs/promises.d.ts +1329 -0
  73. package/vendor/claude-code-acp/node_modules/@types/node/fs.d.ts +4676 -0
  74. package/vendor/claude-code-acp/node_modules/@types/node/globals.d.ts +150 -0
  75. package/vendor/claude-code-acp/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  76. package/vendor/claude-code-acp/node_modules/@types/node/http.d.ts +2167 -0
  77. package/vendor/claude-code-acp/node_modules/@types/node/http2.d.ts +2480 -0
  78. package/vendor/claude-code-acp/node_modules/@types/node/https.d.ts +405 -0
  79. package/vendor/claude-code-acp/node_modules/@types/node/index.d.ts +115 -0
  80. package/vendor/claude-code-acp/node_modules/@types/node/inspector/promises.d.ts +41 -0
  81. package/vendor/claude-code-acp/node_modules/@types/node/inspector.d.ts +224 -0
  82. package/vendor/claude-code-acp/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  83. package/vendor/claude-code-acp/node_modules/@types/node/module.d.ts +819 -0
  84. package/vendor/claude-code-acp/node_modules/@types/node/net.d.ts +933 -0
  85. package/vendor/claude-code-acp/node_modules/@types/node/os.d.ts +507 -0
  86. package/vendor/claude-code-acp/node_modules/@types/node/package.json +155 -0
  87. package/vendor/claude-code-acp/node_modules/@types/node/path/posix.d.ts +8 -0
  88. package/vendor/claude-code-acp/node_modules/@types/node/path/win32.d.ts +8 -0
  89. package/vendor/claude-code-acp/node_modules/@types/node/path.d.ts +187 -0
  90. package/vendor/claude-code-acp/node_modules/@types/node/perf_hooks.d.ts +643 -0
  91. package/vendor/claude-code-acp/node_modules/@types/node/process.d.ts +2161 -0
  92. package/vendor/claude-code-acp/node_modules/@types/node/punycode.d.ts +117 -0
  93. package/vendor/claude-code-acp/node_modules/@types/node/querystring.d.ts +152 -0
  94. package/vendor/claude-code-acp/node_modules/@types/node/quic.d.ts +910 -0
  95. package/vendor/claude-code-acp/node_modules/@types/node/readline/promises.d.ts +161 -0
  96. package/vendor/claude-code-acp/node_modules/@types/node/readline.d.ts +541 -0
  97. package/vendor/claude-code-acp/node_modules/@types/node/repl.d.ts +415 -0
  98. package/vendor/claude-code-acp/node_modules/@types/node/sea.d.ts +162 -0
  99. package/vendor/claude-code-acp/node_modules/@types/node/sqlite.d.ts +955 -0
  100. package/vendor/claude-code-acp/node_modules/@types/node/stream/consumers.d.ts +38 -0
  101. package/vendor/claude-code-acp/node_modules/@types/node/stream/promises.d.ts +211 -0
  102. package/vendor/claude-code-acp/node_modules/@types/node/stream/web.d.ts +296 -0
  103. package/vendor/claude-code-acp/node_modules/@types/node/stream.d.ts +1760 -0
  104. package/vendor/claude-code-acp/node_modules/@types/node/string_decoder.d.ts +67 -0
  105. package/vendor/claude-code-acp/node_modules/@types/node/test/reporters.d.ts +96 -0
  106. package/vendor/claude-code-acp/node_modules/@types/node/test.d.ts +2240 -0
  107. package/vendor/claude-code-acp/node_modules/@types/node/timers/promises.d.ts +108 -0
  108. package/vendor/claude-code-acp/node_modules/@types/node/timers.d.ts +159 -0
  109. package/vendor/claude-code-acp/node_modules/@types/node/tls.d.ts +1198 -0
  110. package/vendor/claude-code-acp/node_modules/@types/node/trace_events.d.ts +197 -0
  111. package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  112. package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  113. package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  114. package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  115. package/vendor/claude-code-acp/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  116. package/vendor/claude-code-acp/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  117. package/vendor/claude-code-acp/node_modules/@types/node/tty.d.ts +250 -0
  118. package/vendor/claude-code-acp/node_modules/@types/node/url.d.ts +519 -0
  119. package/vendor/claude-code-acp/node_modules/@types/node/util/types.d.ts +558 -0
  120. package/vendor/claude-code-acp/node_modules/@types/node/util.d.ts +1662 -0
  121. package/vendor/claude-code-acp/node_modules/@types/node/v8.d.ts +983 -0
  122. package/vendor/claude-code-acp/node_modules/@types/node/vm.d.ts +1208 -0
  123. package/vendor/claude-code-acp/node_modules/@types/node/wasi.d.ts +202 -0
  124. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  125. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  126. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/console.d.ts +9 -0
  127. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  128. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  129. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  130. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/events.d.ts +106 -0
  131. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  132. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  133. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  134. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  135. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  136. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  137. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  138. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  139. package/vendor/claude-code-acp/node_modules/@types/node/web-globals/url.d.ts +24 -0
  140. package/vendor/claude-code-acp/node_modules/@types/node/worker_threads.d.ts +717 -0
  141. package/vendor/claude-code-acp/node_modules/@types/node/zlib.d.ts +618 -0
  142. package/vendor/claude-code-acp/node_modules/undici-types/LICENSE +21 -0
  143. package/vendor/claude-code-acp/node_modules/undici-types/README.md +6 -0
  144. package/vendor/claude-code-acp/node_modules/undici-types/agent.d.ts +32 -0
  145. package/vendor/claude-code-acp/node_modules/undici-types/api.d.ts +43 -0
  146. package/vendor/claude-code-acp/node_modules/undici-types/balanced-pool.d.ts +29 -0
  147. package/vendor/claude-code-acp/node_modules/undici-types/cache-interceptor.d.ts +172 -0
  148. package/vendor/claude-code-acp/node_modules/undici-types/cache.d.ts +36 -0
  149. package/vendor/claude-code-acp/node_modules/undici-types/client-stats.d.ts +15 -0
  150. package/vendor/claude-code-acp/node_modules/undici-types/client.d.ts +108 -0
  151. package/vendor/claude-code-acp/node_modules/undici-types/connector.d.ts +34 -0
  152. package/vendor/claude-code-acp/node_modules/undici-types/content-type.d.ts +21 -0
  153. package/vendor/claude-code-acp/node_modules/undici-types/cookies.d.ts +30 -0
  154. package/vendor/claude-code-acp/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  155. package/vendor/claude-code-acp/node_modules/undici-types/dispatcher.d.ts +276 -0
  156. package/vendor/claude-code-acp/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  157. package/vendor/claude-code-acp/node_modules/undici-types/errors.d.ts +161 -0
  158. package/vendor/claude-code-acp/node_modules/undici-types/eventsource.d.ts +66 -0
  159. package/vendor/claude-code-acp/node_modules/undici-types/fetch.d.ts +211 -0
  160. package/vendor/claude-code-acp/node_modules/undici-types/formdata.d.ts +108 -0
  161. package/vendor/claude-code-acp/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  162. package/vendor/claude-code-acp/node_modules/undici-types/global-origin.d.ts +7 -0
  163. package/vendor/claude-code-acp/node_modules/undici-types/h2c-client.d.ts +73 -0
  164. package/vendor/claude-code-acp/node_modules/undici-types/handlers.d.ts +15 -0
  165. package/vendor/claude-code-acp/node_modules/undici-types/header.d.ts +160 -0
  166. package/vendor/claude-code-acp/node_modules/undici-types/index.d.ts +80 -0
  167. package/vendor/claude-code-acp/node_modules/undici-types/interceptors.d.ts +39 -0
  168. package/vendor/claude-code-acp/node_modules/undici-types/mock-agent.d.ts +68 -0
  169. package/vendor/claude-code-acp/node_modules/undici-types/mock-call-history.d.ts +111 -0
  170. package/vendor/claude-code-acp/node_modules/undici-types/mock-client.d.ts +27 -0
  171. package/vendor/claude-code-acp/node_modules/undici-types/mock-errors.d.ts +12 -0
  172. package/vendor/claude-code-acp/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  173. package/vendor/claude-code-acp/node_modules/undici-types/mock-pool.d.ts +27 -0
  174. package/vendor/claude-code-acp/node_modules/undici-types/package.json +55 -0
  175. package/vendor/claude-code-acp/node_modules/undici-types/patch.d.ts +29 -0
  176. package/vendor/claude-code-acp/node_modules/undici-types/pool-stats.d.ts +19 -0
  177. package/vendor/claude-code-acp/node_modules/undici-types/pool.d.ts +41 -0
  178. package/vendor/claude-code-acp/node_modules/undici-types/proxy-agent.d.ts +29 -0
  179. package/vendor/claude-code-acp/node_modules/undici-types/readable.d.ts +68 -0
  180. package/vendor/claude-code-acp/node_modules/undici-types/retry-agent.d.ts +8 -0
  181. package/vendor/claude-code-acp/node_modules/undici-types/retry-handler.d.ts +125 -0
  182. package/vendor/claude-code-acp/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  183. package/vendor/claude-code-acp/node_modules/undici-types/util.d.ts +18 -0
  184. package/vendor/claude-code-acp/node_modules/undici-types/utility.d.ts +7 -0
  185. package/vendor/claude-code-acp/node_modules/undici-types/webidl.d.ts +341 -0
  186. package/vendor/claude-code-acp/node_modules/undici-types/websocket.d.ts +186 -0
  187. package/dist/web/assets/index-B0sSFjwT.css +0 -1
  188. package/dist/web/assets/index-Cdb0JMLq.js +0 -13
@@ -2,6 +2,7 @@
2
2
  import express from "express";
3
3
  import { access, readdir } from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import { createRequire } from "node:module";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import { createServer } from "node:http";
7
8
  import { userInfo } from "node:os";
@@ -10,9 +11,10 @@ import { SessionManager } from "./session-manager.js";
10
11
  import { formatError, resolveAllowedPath } from "./server-helpers.js";
11
12
  import { ShellSession } from "./shell-session.js";
12
13
  import { buildSingleFileDiff, buildWorkspaceDiffFilesSnapshot } from "./git-diff.js";
13
- import { buildAccessCookie, createAccessKey, hasAuthorizedAccessCookie, isAccessKeyAuthorized } from "./access-key.js";
14
+ import { buildAccessCookie, hasAuthorizedAccessCookie, isAccessKeyAuthorized } from "./access-key.js";
14
15
  import { findAvailablePort, pickPreferredLanIp } from "./network.js";
15
16
  import { resolveBindMode } from "./launch-mode.js";
17
+ import { resolveAccessKey } from "./access-key-prompt.js";
16
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
19
  const launchCwd = process.cwd();
18
20
  const defaultWorkspacePath = process.env.LEDUO_PATROL_WORKSPACE_PATH ?? launchCwd;
@@ -34,8 +36,9 @@ const listenHost = bindMode === "local" ? "127.0.0.1" : "0.0.0.0";
34
36
  const launchHost = bindMode === "local" ? "127.0.0.1" : pickPreferredLanIp();
35
37
  const launchUser = userInfo().username;
36
38
  const claudeBin = process.env.LEDUO_PATROL_CLAUDE_BIN?.trim() || undefined;
37
- const accessKey = process.env.LEDUO_PATROL_ACCESS_KEY?.trim() || createAccessKey();
38
- const enableShell = process.env.LEDUO_ENABLE_SHELL === "true";
39
+ const agentBinPath = resolveAgentBinPath();
40
+ const accessKey = await resolveAccessKey();
41
+ const enableShell = parseBooleanFlag(process.env.LEDUO_ENABLE_SHELL, true);
39
42
  const allowSkipPermissions = process.env.LEDUO_PATROL_ALLOW_SKIP_PERMISSIONS === "true";
40
43
  const app = express();
41
44
  const server = createServer(app);
@@ -43,6 +46,7 @@ const wss = new WebSocketServer({ server, path: "/ws" });
43
46
  const sessionManager = new SessionManager({
44
47
  allowedRoots,
45
48
  claudeBin,
49
+ agentBinPath,
46
50
  allowSkipPermissions,
47
51
  });
48
52
  await sessionManager.initialize();
@@ -86,11 +90,29 @@ app.get("/api/config", (_req, res) => {
86
90
  launchHost,
87
91
  launchUser,
88
92
  allowSkipPermissions,
93
+ availableSessionEngines: sessionManager.getAvailableEngines(),
94
+ defaultSessionEngine: "cli",
89
95
  });
90
96
  });
91
97
  app.get("/api/state", (_req, res) => {
92
98
  res.json(sessionManager.getStateSnapshot());
93
99
  });
100
+ app.get("/api/session-history", (req, res) => {
101
+ try {
102
+ const clientSessionId = typeof req.query.clientSessionId === "string" ? req.query.clientSessionId : "";
103
+ const before = Number(req.query.before ?? 0);
104
+ const limit = Number(req.query.limit ?? 120);
105
+ if (!clientSessionId) {
106
+ throw new Error("clientSessionId is required");
107
+ }
108
+ res.json(sessionManager.getSessionHistory(clientSessionId, before, limit));
109
+ }
110
+ catch (error) {
111
+ res.status(400).json({
112
+ message: formatError(error),
113
+ });
114
+ }
115
+ });
94
116
  app.get("/api/session-diff/files", async (req, res) => {
95
117
  try {
96
118
  const clientSessionId = typeof req.query.clientSessionId === "string" ? req.query.clientSessionId : "";
@@ -174,8 +196,17 @@ wss.on("connection", (socket, request) => {
174
196
  socket.close(1008, "Unauthorized");
175
197
  return;
176
198
  }
177
- const unsubscribe = sessionManager.subscribe((event) => sendEvent(socket, event));
178
- let shellSession = null;
199
+ const shellSessions = new Map();
200
+ const unsubscribe = sessionManager.subscribe((event) => {
201
+ if (event.type === "session_closed") {
202
+ const shellSession = shellSessions.get(event.payload.clientSessionId);
203
+ if (shellSession) {
204
+ shellSession.kill();
205
+ shellSessions.delete(event.payload.clientSessionId);
206
+ }
207
+ }
208
+ sendEvent(socket, event);
209
+ });
179
210
  socket.on("message", async (raw) => {
180
211
  try {
181
212
  const message = JSON.parse(String(raw));
@@ -187,7 +218,25 @@ wss.on("connection", (socket, request) => {
187
218
  });
188
219
  break;
189
220
  case "create_session":
190
- await sessionManager.createSession(message.payload.workspacePath, message.payload.title, message.payload.allowSkipPermissions);
221
+ await sessionManager.createSession(message.payload.workspacePath, message.payload.title, message.payload.allowSkipPermissions, message.payload.engine ?? "cli");
222
+ break;
223
+ case "switch_engine":
224
+ await sessionManager.switchEngine(message.payload.clientSessionId, message.payload.engine);
225
+ break;
226
+ case "prompt":
227
+ await sessionManager.prompt(message.payload.clientSessionId, message.payload.text, message.payload.modeId, message.payload.images);
228
+ break;
229
+ case "set_mode":
230
+ await sessionManager.setSessionMode(message.payload.clientSessionId, message.payload.modeId);
231
+ break;
232
+ case "cancel":
233
+ await sessionManager.cancel(message.payload.clientSessionId);
234
+ break;
235
+ case "permission":
236
+ await sessionManager.resolvePermission(message.payload.clientSessionId, message.payload.requestId, message.payload.optionId, message.payload.note);
237
+ break;
238
+ case "answer_question":
239
+ await sessionManager.answerQuestion(message.payload.clientSessionId, message.payload.questionId, message.payload.answer);
191
240
  break;
192
241
  case "close_session":
193
242
  await sessionManager.closeSession(message.payload.clientSessionId);
@@ -215,40 +264,44 @@ wss.on("connection", (socket, request) => {
215
264
  if (!enableShell) {
216
265
  throw new Error("Shell feature is disabled. Set LEDUO_ENABLE_SHELL=true to enable it.");
217
266
  }
218
- shellSession?.kill();
219
- shellSession = null;
267
+ const clientSessionId = message.payload.clientSessionId;
268
+ const existingShell = shellSessions.get(clientSessionId);
220
269
  const cols = Math.max(2, message.payload.cols);
221
270
  const rows = Math.max(2, message.payload.rows);
222
- const shellWorkspacePath = sessionManager.getSessionWorkspacePath(message.payload.clientSessionId);
271
+ if (existingShell?.alive) {
272
+ existingShell.resize(cols, rows);
273
+ break;
274
+ }
275
+ const shellWorkspacePath = sessionManager.getSessionWorkspacePath(clientSessionId);
223
276
  const newShell = new ShellSession(shellWorkspacePath, cols, rows);
224
- shellSession = newShell;
277
+ shellSessions.set(clientSessionId, newShell);
225
278
  newShell.on("output", (data) => {
226
279
  if (socket.readyState === WebSocket.OPEN) {
227
- socket.send(JSON.stringify({ type: "shell_output", payload: { data } }));
280
+ socket.send(JSON.stringify({ type: "shell_output", payload: { clientSessionId, data } }));
228
281
  }
229
282
  });
230
283
  newShell.on("exit", (exitCode) => {
231
- if (shellSession === newShell) {
232
- shellSession = null;
284
+ if (shellSessions.get(clientSessionId) === newShell) {
285
+ shellSessions.delete(clientSessionId);
233
286
  }
234
287
  if (socket.readyState === WebSocket.OPEN) {
235
- socket.send(JSON.stringify({ type: "shell_exited", payload: { exitCode } }));
288
+ socket.send(JSON.stringify({ type: "shell_exited", payload: { clientSessionId, exitCode } }));
236
289
  }
237
290
  });
238
291
  break;
239
292
  }
240
293
  case "shell_input":
241
- if (!shellSession?.alive) {
294
+ if (!shellSessions.get(message.payload.clientSessionId)?.alive) {
242
295
  throw new Error("Shell is not running");
243
296
  }
244
- shellSession.write(message.payload.data);
297
+ shellSessions.get(message.payload.clientSessionId)?.write(message.payload.data);
245
298
  break;
246
299
  case "shell_resize":
247
- shellSession?.resize(message.payload.cols, message.payload.rows);
300
+ shellSessions.get(message.payload.clientSessionId)?.resize(message.payload.cols, message.payload.rows);
248
301
  break;
249
302
  case "shell_stop":
250
- shellSession?.kill();
251
- shellSession = null;
303
+ shellSessions.get(message.payload.clientSessionId)?.kill();
304
+ shellSessions.delete(message.payload.clientSessionId);
252
305
  break;
253
306
  }
254
307
  }
@@ -261,8 +314,10 @@ wss.on("connection", (socket, request) => {
261
314
  });
262
315
  socket.on("close", () => {
263
316
  unsubscribe();
264
- shellSession?.kill();
265
- shellSession = null;
317
+ for (const shellSession of shellSessions.values()) {
318
+ shellSession.kill();
319
+ }
320
+ shellSessions.clear();
266
321
  });
267
322
  });
268
323
  const listenPort = await findAvailablePort(requestedPort, listenHost);
@@ -286,6 +341,7 @@ else {
286
341
  console.log("Web UI is unavailable on this start because bundled assets are missing.");
287
342
  }
288
343
  console.log(`Access URL: http://${displayHost}:${accessPort}/?key=${accessKey}`);
344
+ console.log(`Shell feature: ${enableShell ? "enabled" : "disabled"}`);
289
345
  function sendEvent(socket, event) {
290
346
  if (socket.readyState !== WebSocket.OPEN) {
291
347
  return;
@@ -301,3 +357,30 @@ async function hasReadableFile(filePath) {
301
357
  return false;
302
358
  }
303
359
  }
360
+ function parseBooleanFlag(rawValue, defaultValue) {
361
+ if (rawValue == null || rawValue.trim() === "") {
362
+ return defaultValue;
363
+ }
364
+ const normalized = rawValue.trim().toLowerCase();
365
+ if (["1", "true", "yes", "on"].includes(normalized)) {
366
+ return true;
367
+ }
368
+ if (["0", "false", "no", "off"].includes(normalized)) {
369
+ return false;
370
+ }
371
+ return defaultValue;
372
+ }
373
+ function resolveAgentBinPath() {
374
+ if (process.env.LEDUO_PATROL_AGENT_BIN?.trim()) {
375
+ return process.env.LEDUO_PATROL_AGENT_BIN.trim();
376
+ }
377
+ const require = createRequire(import.meta.url);
378
+ try {
379
+ const pkgPath = require.resolve("@zed-industries/claude-code-acp/package.json");
380
+ const pkgDir = path.dirname(pkgPath);
381
+ return path.join(pkgDir, "dist", "index.js");
382
+ }
383
+ catch {
384
+ return undefined;
385
+ }
386
+ }
@@ -1,9 +1,6 @@
1
- import os from "node:os";
2
- import path from "node:path";
3
- import { access, mkdir, readFile, writeFile } from "node:fs/promises";
4
1
  import readline from "node:readline/promises";
2
+ import { loadStartupPreferences, saveStartupPreferences } from "./startup-preferences.js";
5
3
  const DEFAULT_MODE = "server";
6
- const PREFS_FILE_PATH = path.join(os.homedir(), ".leduo-patrol", "launch-preferences.json");
7
4
  export async function resolveBindMode(options = {}) {
8
5
  const argv = options.argv ?? process.argv.slice(2);
9
6
  const stdin = options.stdin ?? process.stdin;
@@ -41,7 +38,7 @@ function parseBindMode(raw) {
41
38
  }
42
39
  return null;
43
40
  }
44
- function readOptionValue(argv, optionName) {
41
+ export function readOptionValue(argv, optionName) {
45
42
  for (let i = 0; i < argv.length; i += 1) {
46
43
  const arg = argv[i];
47
44
  if (arg === optionName) {
@@ -77,12 +74,8 @@ async function promptShouldRemember(stdin, stdout) {
77
74
  }
78
75
  }
79
76
  async function loadRememberedMode() {
80
- if (!(await isReadable(PREFS_FILE_PATH))) {
81
- return null;
82
- }
83
77
  try {
84
- const raw = await readFile(PREFS_FILE_PATH, "utf8");
85
- const parsed = JSON.parse(raw);
78
+ const parsed = (await loadStartupPreferences());
86
79
  return parseBindMode(parsed.bindMode ?? "");
87
80
  }
88
81
  catch {
@@ -90,18 +83,7 @@ async function loadRememberedMode() {
90
83
  }
91
84
  }
92
85
  async function saveRememberedMode(mode) {
93
- await mkdir(path.dirname(PREFS_FILE_PATH), { recursive: true });
94
- const payload = { bindMode: mode };
95
- await writeFile(PREFS_FILE_PATH, JSON.stringify(payload, null, 2), "utf8");
96
- }
97
- async function isReadable(filePath) {
98
- try {
99
- await access(filePath);
100
- return true;
101
- }
102
- catch {
103
- return false;
104
- }
86
+ await saveStartupPreferences({ bindMode: mode });
105
87
  }
106
88
  export const launchModeTestables = {
107
89
  parseBindMode,
@@ -0,0 +1,28 @@
1
+ import { chmodSync, existsSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { createRequire } from "node:module";
4
+ const require = createRequire(import.meta.url);
5
+ export function ensureNodePtySpawnHelperExecutable() {
6
+ if (process.platform !== "darwin") {
7
+ return;
8
+ }
9
+ const helperPath = resolveNodePtySpawnHelperPath();
10
+ if (!helperPath) {
11
+ return;
12
+ }
13
+ ensureExecutableBit(helperPath);
14
+ }
15
+ export function resolveNodePtySpawnHelperPath() {
16
+ const packageJsonPath = require.resolve("node-pty/package.json");
17
+ const packageRoot = path.dirname(packageJsonPath);
18
+ const helperPath = path.join(packageRoot, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper");
19
+ return existsSync(helperPath) ? helperPath : null;
20
+ }
21
+ export function ensureExecutableBit(filePath) {
22
+ const stats = statSync(filePath);
23
+ if ((stats.mode & 0o111) === 0o111) {
24
+ return false;
25
+ }
26
+ chmodSync(filePath, stats.mode | 0o755);
27
+ return true;
28
+ }