leduo-patrol 2.2.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.
- package/README.md +11 -2
- package/dist/server/__tests__/acp-session.test.js +92 -0
- package/dist/server/__tests__/activity-monitor.test.js +13 -1
- package/dist/server/__tests__/session-manager.test.js +215 -1
- package/dist/server/acp-session.js +476 -0
- package/dist/server/activity-monitor.js +22 -7
- package/dist/server/index.js +54 -1
- package/dist/server/session-manager.js +1117 -121
- package/dist/web/assets/index-B-YXVUoQ.css +1 -0
- package/dist/web/assets/index-Bu0K7QgY.js +21 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -1
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/LICENSE +191 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/README.md +53 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.d.ts +823 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.js +965 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.test.d.ts +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.test.js +839 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.test.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/agent.d.ts +2 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js +225 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/client.d.ts +2 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/client.js +130 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/client.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.d.ts +35 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js +5 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/index.d.ts +27 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/index.js +28 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/index.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.d.ts +2870 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js +3 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.d.ts +5333 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js +1554 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/stream.d.ts +24 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/stream.js +64 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/stream.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/package.json +66 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/schema/schema.json +4125 -0
- package/vendor/claude-code-acp/node_modules/@types/node/LICENSE +21 -0
- package/vendor/claude-code-acp/node_modules/@types/node/README.md +15 -0
- package/vendor/claude-code-acp/node_modules/@types/node/assert/strict.d.ts +105 -0
- package/vendor/claude-code-acp/node_modules/@types/node/assert.d.ts +955 -0
- package/vendor/claude-code-acp/node_modules/@types/node/async_hooks.d.ts +623 -0
- package/vendor/claude-code-acp/node_modules/@types/node/buffer.buffer.d.ts +466 -0
- package/vendor/claude-code-acp/node_modules/@types/node/buffer.d.ts +1810 -0
- package/vendor/claude-code-acp/node_modules/@types/node/child_process.d.ts +1428 -0
- package/vendor/claude-code-acp/node_modules/@types/node/cluster.d.ts +486 -0
- package/vendor/claude-code-acp/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
- package/vendor/claude-code-acp/node_modules/@types/node/console.d.ts +151 -0
- package/vendor/claude-code-acp/node_modules/@types/node/constants.d.ts +20 -0
- package/vendor/claude-code-acp/node_modules/@types/node/crypto.d.ts +4065 -0
- package/vendor/claude-code-acp/node_modules/@types/node/dgram.d.ts +564 -0
- package/vendor/claude-code-acp/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
- package/vendor/claude-code-acp/node_modules/@types/node/dns/promises.d.ts +503 -0
- package/vendor/claude-code-acp/node_modules/@types/node/dns.d.ts +922 -0
- package/vendor/claude-code-acp/node_modules/@types/node/domain.d.ts +166 -0
- package/vendor/claude-code-acp/node_modules/@types/node/events.d.ts +1054 -0
- package/vendor/claude-code-acp/node_modules/@types/node/fs/promises.d.ts +1329 -0
- package/vendor/claude-code-acp/node_modules/@types/node/fs.d.ts +4676 -0
- package/vendor/claude-code-acp/node_modules/@types/node/globals.d.ts +150 -0
- package/vendor/claude-code-acp/node_modules/@types/node/globals.typedarray.d.ts +101 -0
- package/vendor/claude-code-acp/node_modules/@types/node/http.d.ts +2167 -0
- package/vendor/claude-code-acp/node_modules/@types/node/http2.d.ts +2480 -0
- package/vendor/claude-code-acp/node_modules/@types/node/https.d.ts +405 -0
- package/vendor/claude-code-acp/node_modules/@types/node/index.d.ts +115 -0
- package/vendor/claude-code-acp/node_modules/@types/node/inspector/promises.d.ts +41 -0
- package/vendor/claude-code-acp/node_modules/@types/node/inspector.d.ts +224 -0
- package/vendor/claude-code-acp/node_modules/@types/node/inspector.generated.d.ts +4226 -0
- package/vendor/claude-code-acp/node_modules/@types/node/module.d.ts +819 -0
- package/vendor/claude-code-acp/node_modules/@types/node/net.d.ts +933 -0
- package/vendor/claude-code-acp/node_modules/@types/node/os.d.ts +507 -0
- package/vendor/claude-code-acp/node_modules/@types/node/package.json +155 -0
- package/vendor/claude-code-acp/node_modules/@types/node/path/posix.d.ts +8 -0
- package/vendor/claude-code-acp/node_modules/@types/node/path/win32.d.ts +8 -0
- package/vendor/claude-code-acp/node_modules/@types/node/path.d.ts +187 -0
- package/vendor/claude-code-acp/node_modules/@types/node/perf_hooks.d.ts +643 -0
- package/vendor/claude-code-acp/node_modules/@types/node/process.d.ts +2161 -0
- package/vendor/claude-code-acp/node_modules/@types/node/punycode.d.ts +117 -0
- package/vendor/claude-code-acp/node_modules/@types/node/querystring.d.ts +152 -0
- package/vendor/claude-code-acp/node_modules/@types/node/quic.d.ts +910 -0
- package/vendor/claude-code-acp/node_modules/@types/node/readline/promises.d.ts +161 -0
- package/vendor/claude-code-acp/node_modules/@types/node/readline.d.ts +541 -0
- package/vendor/claude-code-acp/node_modules/@types/node/repl.d.ts +415 -0
- package/vendor/claude-code-acp/node_modules/@types/node/sea.d.ts +162 -0
- package/vendor/claude-code-acp/node_modules/@types/node/sqlite.d.ts +955 -0
- package/vendor/claude-code-acp/node_modules/@types/node/stream/consumers.d.ts +38 -0
- package/vendor/claude-code-acp/node_modules/@types/node/stream/promises.d.ts +211 -0
- package/vendor/claude-code-acp/node_modules/@types/node/stream/web.d.ts +296 -0
- package/vendor/claude-code-acp/node_modules/@types/node/stream.d.ts +1760 -0
- package/vendor/claude-code-acp/node_modules/@types/node/string_decoder.d.ts +67 -0
- package/vendor/claude-code-acp/node_modules/@types/node/test/reporters.d.ts +96 -0
- package/vendor/claude-code-acp/node_modules/@types/node/test.d.ts +2240 -0
- package/vendor/claude-code-acp/node_modules/@types/node/timers/promises.d.ts +108 -0
- package/vendor/claude-code-acp/node_modules/@types/node/timers.d.ts +159 -0
- package/vendor/claude-code-acp/node_modules/@types/node/tls.d.ts +1198 -0
- package/vendor/claude-code-acp/node_modules/@types/node/trace_events.d.ts +197 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/index.d.ts +117 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.7/index.d.ts +117 -0
- package/vendor/claude-code-acp/node_modules/@types/node/tty.d.ts +250 -0
- package/vendor/claude-code-acp/node_modules/@types/node/url.d.ts +519 -0
- package/vendor/claude-code-acp/node_modules/@types/node/util/types.d.ts +558 -0
- package/vendor/claude-code-acp/node_modules/@types/node/util.d.ts +1662 -0
- package/vendor/claude-code-acp/node_modules/@types/node/v8.d.ts +983 -0
- package/vendor/claude-code-acp/node_modules/@types/node/vm.d.ts +1208 -0
- package/vendor/claude-code-acp/node_modules/@types/node/wasi.d.ts +202 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/blob.d.ts +23 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/console.d.ts +9 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/events.d.ts +106 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/performance.d.ts +45 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/storage.d.ts +24 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/streams.d.ts +115 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/timers.d.ts +44 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/url.d.ts +24 -0
- package/vendor/claude-code-acp/node_modules/@types/node/worker_threads.d.ts +717 -0
- package/vendor/claude-code-acp/node_modules/@types/node/zlib.d.ts +618 -0
- package/vendor/claude-code-acp/node_modules/undici-types/LICENSE +21 -0
- package/vendor/claude-code-acp/node_modules/undici-types/README.md +6 -0
- package/vendor/claude-code-acp/node_modules/undici-types/agent.d.ts +32 -0
- package/vendor/claude-code-acp/node_modules/undici-types/api.d.ts +43 -0
- package/vendor/claude-code-acp/node_modules/undici-types/balanced-pool.d.ts +29 -0
- package/vendor/claude-code-acp/node_modules/undici-types/cache-interceptor.d.ts +172 -0
- package/vendor/claude-code-acp/node_modules/undici-types/cache.d.ts +36 -0
- package/vendor/claude-code-acp/node_modules/undici-types/client-stats.d.ts +15 -0
- package/vendor/claude-code-acp/node_modules/undici-types/client.d.ts +108 -0
- package/vendor/claude-code-acp/node_modules/undici-types/connector.d.ts +34 -0
- package/vendor/claude-code-acp/node_modules/undici-types/content-type.d.ts +21 -0
- package/vendor/claude-code-acp/node_modules/undici-types/cookies.d.ts +30 -0
- package/vendor/claude-code-acp/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
- package/vendor/claude-code-acp/node_modules/undici-types/dispatcher.d.ts +276 -0
- package/vendor/claude-code-acp/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
- package/vendor/claude-code-acp/node_modules/undici-types/errors.d.ts +161 -0
- package/vendor/claude-code-acp/node_modules/undici-types/eventsource.d.ts +66 -0
- package/vendor/claude-code-acp/node_modules/undici-types/fetch.d.ts +211 -0
- package/vendor/claude-code-acp/node_modules/undici-types/formdata.d.ts +108 -0
- package/vendor/claude-code-acp/node_modules/undici-types/global-dispatcher.d.ts +9 -0
- package/vendor/claude-code-acp/node_modules/undici-types/global-origin.d.ts +7 -0
- package/vendor/claude-code-acp/node_modules/undici-types/h2c-client.d.ts +73 -0
- package/vendor/claude-code-acp/node_modules/undici-types/handlers.d.ts +15 -0
- package/vendor/claude-code-acp/node_modules/undici-types/header.d.ts +160 -0
- package/vendor/claude-code-acp/node_modules/undici-types/index.d.ts +80 -0
- package/vendor/claude-code-acp/node_modules/undici-types/interceptors.d.ts +39 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-agent.d.ts +68 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-call-history.d.ts +111 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-client.d.ts +27 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-errors.d.ts +12 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-interceptor.d.ts +94 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-pool.d.ts +27 -0
- package/vendor/claude-code-acp/node_modules/undici-types/package.json +55 -0
- package/vendor/claude-code-acp/node_modules/undici-types/patch.d.ts +29 -0
- package/vendor/claude-code-acp/node_modules/undici-types/pool-stats.d.ts +19 -0
- package/vendor/claude-code-acp/node_modules/undici-types/pool.d.ts +41 -0
- package/vendor/claude-code-acp/node_modules/undici-types/proxy-agent.d.ts +29 -0
- package/vendor/claude-code-acp/node_modules/undici-types/readable.d.ts +68 -0
- package/vendor/claude-code-acp/node_modules/undici-types/retry-agent.d.ts +8 -0
- package/vendor/claude-code-acp/node_modules/undici-types/retry-handler.d.ts +125 -0
- package/vendor/claude-code-acp/node_modules/undici-types/snapshot-agent.d.ts +109 -0
- package/vendor/claude-code-acp/node_modules/undici-types/util.d.ts +18 -0
- package/vendor/claude-code-acp/node_modules/undici-types/utility.d.ts +7 -0
- package/vendor/claude-code-acp/node_modules/undici-types/webidl.d.ts +341 -0
- package/vendor/claude-code-acp/node_modules/undici-types/websocket.d.ts +186 -0
- package/dist/web/assets/index-B5Dh2E8j.css +0 -1
- package/dist/web/assets/index-xPPPaEde.js +0 -13
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as childProcess from "node:child_process";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { Readable, Writable } from "node:stream";
|
|
6
|
+
import * as acp from "@agentclientprotocol/sdk";
|
|
7
|
+
import { buildSpawnFailureMessage } from "./server-helpers.js";
|
|
8
|
+
export const acpSessionTestables = {
|
|
9
|
+
spawnAgent(agentBinPath, options) {
|
|
10
|
+
return childProcess.spawn(agentBinPath, [], options);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export class ClaudeAcpSession {
|
|
14
|
+
workspacePath;
|
|
15
|
+
agentBinPath;
|
|
16
|
+
claudeBin;
|
|
17
|
+
onEvent;
|
|
18
|
+
pendingPermissions = new Map();
|
|
19
|
+
pendingQuestions = new Map();
|
|
20
|
+
agentProcess = null;
|
|
21
|
+
connection = null;
|
|
22
|
+
sessionId = null;
|
|
23
|
+
activePrompt = false;
|
|
24
|
+
disposing = false;
|
|
25
|
+
connectPromise = null;
|
|
26
|
+
sessionPromise = null;
|
|
27
|
+
currentModeId = null;
|
|
28
|
+
drainTimer = null;
|
|
29
|
+
drainResolve = null;
|
|
30
|
+
static DRAIN_QUIET_MS = 200;
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.workspacePath = options.workspacePath;
|
|
33
|
+
this.agentBinPath = options.agentBinPath;
|
|
34
|
+
this.claudeBin = options.claudeBin;
|
|
35
|
+
this.onEvent = options.onEvent;
|
|
36
|
+
}
|
|
37
|
+
async connect() {
|
|
38
|
+
if (this.connectPromise) {
|
|
39
|
+
await this.connectPromise;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (this.connection) {
|
|
43
|
+
this.emitReady();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.connectPromise = (async () => {
|
|
47
|
+
await mkdir(this.workspacePath, { recursive: true });
|
|
48
|
+
const agentEnv = {
|
|
49
|
+
...process.env,
|
|
50
|
+
...(this.claudeBin ? { CLAUDE_CODE_EXECUTABLE: this.claudeBin } : undefined),
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
this.agentProcess = acpSessionTestables.spawnAgent(this.agentBinPath, {
|
|
54
|
+
cwd: this.workspacePath,
|
|
55
|
+
env: agentEnv,
|
|
56
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw new Error(this.buildAgentSpawnFailureMessage(error));
|
|
61
|
+
}
|
|
62
|
+
let startupComplete = false;
|
|
63
|
+
let processHandled = false;
|
|
64
|
+
let rejectStartup = null;
|
|
65
|
+
const startupFailure = new Promise((_, reject) => {
|
|
66
|
+
rejectStartup = reject;
|
|
67
|
+
});
|
|
68
|
+
const handleAgentFailure = (error) => {
|
|
69
|
+
if (processHandled) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
processHandled = true;
|
|
73
|
+
this.connection = null;
|
|
74
|
+
this.sessionId = null;
|
|
75
|
+
this.sessionPromise = null;
|
|
76
|
+
this.connectPromise = null;
|
|
77
|
+
this.currentModeId = null;
|
|
78
|
+
this.activePrompt = false;
|
|
79
|
+
this.clearDrain();
|
|
80
|
+
this.rejectPendingPermissions(new Error("Permission request cancelled because ACP agent stopped."));
|
|
81
|
+
this.rejectPendingQuestions(new Error("Question cancelled because ACP agent stopped."));
|
|
82
|
+
this.agentProcess = null;
|
|
83
|
+
if (this.disposing) {
|
|
84
|
+
this.disposing = false;
|
|
85
|
+
if (!startupComplete) {
|
|
86
|
+
rejectStartup?.(error);
|
|
87
|
+
rejectStartup = null;
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!startupComplete) {
|
|
92
|
+
rejectStartup?.(error);
|
|
93
|
+
rejectStartup = null;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.onEvent({
|
|
97
|
+
type: "error",
|
|
98
|
+
payload: { message: error.message, fatal: true },
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
this.agentProcess.stderr.on("data", (chunk) => {
|
|
102
|
+
const message = chunk.toString().trim();
|
|
103
|
+
if (!message || this.shouldIgnoreAgentStderr(message) || this.shouldIgnoreToolOutputLog(message)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.onEvent({ type: "error", payload: { message, fatal: false } });
|
|
107
|
+
});
|
|
108
|
+
this.agentProcess.on("error", (error) => {
|
|
109
|
+
handleAgentFailure(new Error(this.buildAgentSpawnFailureMessage(error)));
|
|
110
|
+
});
|
|
111
|
+
this.agentProcess.on("exit", (code, signal) => {
|
|
112
|
+
handleAgentFailure(new Error(`Claude ACP agent exited (${code ?? "null"} / ${signal ?? "null"}).`));
|
|
113
|
+
});
|
|
114
|
+
const input = Writable.toWeb(this.agentProcess.stdin);
|
|
115
|
+
const output = Readable.toWeb(this.agentProcess.stdout);
|
|
116
|
+
const stream = acp.ndJsonStream(input, output);
|
|
117
|
+
const client = {
|
|
118
|
+
requestPermission: async (params) => this.handlePermissionRequest(params),
|
|
119
|
+
sessionUpdate: async (params) => {
|
|
120
|
+
const update = params.update;
|
|
121
|
+
if (update.sessionUpdate === "current_mode_update" && typeof update.currentModeId === "string") {
|
|
122
|
+
this.currentModeId = update.currentModeId;
|
|
123
|
+
}
|
|
124
|
+
this.onEvent({ type: "session_update", payload: params.update });
|
|
125
|
+
this.resetDrainTimer();
|
|
126
|
+
},
|
|
127
|
+
readTextFile: async (params) => this.handleReadTextFile(params),
|
|
128
|
+
writeTextFile: async (params) => this.handleWriteTextFile(params),
|
|
129
|
+
extMethod: async (method, params) => this.handleExtMethod(method, params),
|
|
130
|
+
};
|
|
131
|
+
this.connection = new acp.ClientSideConnection(() => client, stream);
|
|
132
|
+
await Promise.race([
|
|
133
|
+
this.connection.initialize({
|
|
134
|
+
protocolVersion: acp.PROTOCOL_VERSION,
|
|
135
|
+
clientCapabilities: {
|
|
136
|
+
fs: { readTextFile: true, writeTextFile: true },
|
|
137
|
+
_meta: {
|
|
138
|
+
extensions: [
|
|
139
|
+
{
|
|
140
|
+
method: "leduo/ask_question",
|
|
141
|
+
description: "Ask the user a question with optional multiple-choice options. " +
|
|
142
|
+
"Params: { question: string, options?: Array<{ id: string, label: string }>, allowCustomAnswer?: boolean }. " +
|
|
143
|
+
"Returns: { answer: string }.",
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
startupFailure,
|
|
150
|
+
]);
|
|
151
|
+
startupComplete = true;
|
|
152
|
+
rejectStartup = null;
|
|
153
|
+
this.emitReady();
|
|
154
|
+
})();
|
|
155
|
+
try {
|
|
156
|
+
await this.connectPromise;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
this.connectPromise = null;
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async ensureSession() {
|
|
164
|
+
if (this.sessionPromise) {
|
|
165
|
+
return await this.sessionPromise;
|
|
166
|
+
}
|
|
167
|
+
if (!this.connection) {
|
|
168
|
+
await this.connect();
|
|
169
|
+
}
|
|
170
|
+
if (this.sessionId || !this.connection) {
|
|
171
|
+
return this.sessionId;
|
|
172
|
+
}
|
|
173
|
+
this.sessionPromise = (async () => {
|
|
174
|
+
const response = await this.connection.newSession({
|
|
175
|
+
cwd: this.workspacePath,
|
|
176
|
+
mcpServers: [],
|
|
177
|
+
});
|
|
178
|
+
this.sessionId = response.sessionId;
|
|
179
|
+
this.currentModeId = null;
|
|
180
|
+
this.onEvent({
|
|
181
|
+
type: "session_created",
|
|
182
|
+
payload: {
|
|
183
|
+
sessionId: response.sessionId,
|
|
184
|
+
modes: response.modes?.availableModes.map((mode) => mode.id) ?? [],
|
|
185
|
+
configOptions: response.configOptions ?? [],
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
return this.sessionId;
|
|
189
|
+
})();
|
|
190
|
+
try {
|
|
191
|
+
return await this.sessionPromise;
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
this.sessionPromise = null;
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async loadSession(existingSessionId) {
|
|
199
|
+
if (!this.connection) {
|
|
200
|
+
await this.connect();
|
|
201
|
+
}
|
|
202
|
+
if (!this.connection) {
|
|
203
|
+
throw new Error("ACP connection is not available.");
|
|
204
|
+
}
|
|
205
|
+
this.sessionId = existingSessionId;
|
|
206
|
+
this.sessionPromise = Promise.resolve(existingSessionId);
|
|
207
|
+
const response = await this.connection.loadSession({
|
|
208
|
+
sessionId: existingSessionId,
|
|
209
|
+
cwd: this.workspacePath,
|
|
210
|
+
mcpServers: [],
|
|
211
|
+
});
|
|
212
|
+
this.currentModeId = response.modes?.currentModeId ?? null;
|
|
213
|
+
this.onEvent({
|
|
214
|
+
type: "session_restored",
|
|
215
|
+
payload: {
|
|
216
|
+
sessionId: existingSessionId,
|
|
217
|
+
modes: response.modes?.availableModes.map((mode) => mode.id) ?? [],
|
|
218
|
+
configOptions: response.configOptions ?? [],
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
return existingSessionId;
|
|
222
|
+
}
|
|
223
|
+
async findRestorableSession(preferredSessionId) {
|
|
224
|
+
if (!this.connection) {
|
|
225
|
+
await this.connect();
|
|
226
|
+
}
|
|
227
|
+
if (!this.connection) {
|
|
228
|
+
throw new Error("ACP connection is not available.");
|
|
229
|
+
}
|
|
230
|
+
const response = await this.connection.unstable_listSessions({
|
|
231
|
+
cwd: this.workspacePath,
|
|
232
|
+
});
|
|
233
|
+
if (preferredSessionId) {
|
|
234
|
+
const exactMatch = response.sessions.find((session) => session.sessionId === preferredSessionId);
|
|
235
|
+
return exactMatch?.sessionId ?? null;
|
|
236
|
+
}
|
|
237
|
+
return response.sessions[0]?.sessionId ?? null;
|
|
238
|
+
}
|
|
239
|
+
async prompt(text, images) {
|
|
240
|
+
const sessionId = await this.ensureSession();
|
|
241
|
+
if (!this.connection || !sessionId) {
|
|
242
|
+
throw new Error("ACP session is not available.");
|
|
243
|
+
}
|
|
244
|
+
if (this.activePrompt) {
|
|
245
|
+
throw new Error("Another Claude prompt is still running.");
|
|
246
|
+
}
|
|
247
|
+
this.activePrompt = true;
|
|
248
|
+
const promptId = randomUUID();
|
|
249
|
+
this.onEvent({ type: "prompt_started", payload: { promptId, text } });
|
|
250
|
+
try {
|
|
251
|
+
const promptContent = [];
|
|
252
|
+
if (images && images.length > 0) {
|
|
253
|
+
for (const img of images) {
|
|
254
|
+
promptContent.push({ type: "image", data: img.data, mimeType: img.mimeType });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
promptContent.push({ type: "text", text });
|
|
258
|
+
const response = await this.connection.prompt({
|
|
259
|
+
sessionId,
|
|
260
|
+
messageId: randomUUID(),
|
|
261
|
+
prompt: promptContent,
|
|
262
|
+
});
|
|
263
|
+
await this.waitForDrain();
|
|
264
|
+
this.onEvent({
|
|
265
|
+
type: "prompt_finished",
|
|
266
|
+
payload: { promptId, stopReason: response.stopReason },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
this.activePrompt = false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async setMode(modeId) {
|
|
274
|
+
const sessionId = await this.ensureSession();
|
|
275
|
+
if (!this.connection || !sessionId || !modeId || this.currentModeId === modeId) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
await this.connection.setSessionMode({
|
|
279
|
+
sessionId,
|
|
280
|
+
modeId,
|
|
281
|
+
});
|
|
282
|
+
this.currentModeId = modeId;
|
|
283
|
+
}
|
|
284
|
+
async cancel() {
|
|
285
|
+
if (!this.connection || !this.sessionId || !this.activePrompt) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
await this.connection.cancel({ sessionId: this.sessionId });
|
|
289
|
+
}
|
|
290
|
+
async resolvePermission(requestId, optionId, note) {
|
|
291
|
+
const pending = this.pendingPermissions.get(requestId);
|
|
292
|
+
if (!pending) {
|
|
293
|
+
throw new Error("Permission request was not found or already resolved.");
|
|
294
|
+
}
|
|
295
|
+
pending.resolve({
|
|
296
|
+
outcome: {
|
|
297
|
+
outcome: "selected",
|
|
298
|
+
optionId,
|
|
299
|
+
_meta: note && note.trim() ? { note: note.trim() } : undefined,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
this.pendingPermissions.delete(requestId);
|
|
303
|
+
this.onEvent({ type: "permission_resolved", payload: { requestId, optionId } });
|
|
304
|
+
}
|
|
305
|
+
async answerQuestion(questionId, answer) {
|
|
306
|
+
const pending = this.pendingQuestions.get(questionId);
|
|
307
|
+
if (!pending) {
|
|
308
|
+
throw new Error("Question was not found or already answered.");
|
|
309
|
+
}
|
|
310
|
+
pending.resolve({ answer });
|
|
311
|
+
this.pendingQuestions.delete(questionId);
|
|
312
|
+
this.onEvent({ type: "question_answered", payload: { questionId, answer } });
|
|
313
|
+
}
|
|
314
|
+
async dispose() {
|
|
315
|
+
this.disposing = true;
|
|
316
|
+
this.clearDrain();
|
|
317
|
+
this.rejectPendingPermissions(new Error("Client disconnected."));
|
|
318
|
+
this.rejectPendingQuestions(new Error("Client disconnected."));
|
|
319
|
+
if (this.agentProcess && !this.agentProcess.killed) {
|
|
320
|
+
this.agentProcess.kill();
|
|
321
|
+
}
|
|
322
|
+
this.agentProcess = null;
|
|
323
|
+
this.connection = null;
|
|
324
|
+
this.sessionId = null;
|
|
325
|
+
this.sessionPromise = null;
|
|
326
|
+
this.connectPromise = null;
|
|
327
|
+
this.currentModeId = null;
|
|
328
|
+
this.activePrompt = false;
|
|
329
|
+
}
|
|
330
|
+
emitReady() {
|
|
331
|
+
this.onEvent({
|
|
332
|
+
type: "ready",
|
|
333
|
+
payload: { workspacePath: this.workspacePath, agentConnected: true },
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
buildAgentSpawnFailureMessage(error) {
|
|
337
|
+
const hint = (error instanceof Error
|
|
338
|
+
&& "code" in error
|
|
339
|
+
&& error.code === "EAGAIN")
|
|
340
|
+
? "The OS temporarily refused to start a new process (EAGAIN). Try again and check system process/thread limits or other running agent processes."
|
|
341
|
+
: "Check that LEDUO_PATROL_AGENT_BIN points to a valid ACP agent, or that the bundled claude-code-acp agent is executable.";
|
|
342
|
+
return buildSpawnFailureMessage("Claude ACP agent", this.agentBinPath, this.workspacePath, error, hint);
|
|
343
|
+
}
|
|
344
|
+
waitForDrain() {
|
|
345
|
+
return new Promise((resolve) => {
|
|
346
|
+
this.drainResolve = resolve;
|
|
347
|
+
this.drainTimer = setTimeout(() => {
|
|
348
|
+
this.drainResolve = null;
|
|
349
|
+
this.drainTimer = null;
|
|
350
|
+
resolve();
|
|
351
|
+
}, ClaudeAcpSession.DRAIN_QUIET_MS);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
resetDrainTimer() {
|
|
355
|
+
if (this.drainTimer && this.drainResolve) {
|
|
356
|
+
clearTimeout(this.drainTimer);
|
|
357
|
+
const resolve = this.drainResolve;
|
|
358
|
+
this.drainTimer = setTimeout(() => {
|
|
359
|
+
this.drainResolve = null;
|
|
360
|
+
this.drainTimer = null;
|
|
361
|
+
resolve();
|
|
362
|
+
}, ClaudeAcpSession.DRAIN_QUIET_MS);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
clearDrain() {
|
|
366
|
+
if (this.drainTimer) {
|
|
367
|
+
clearTimeout(this.drainTimer);
|
|
368
|
+
this.drainTimer = null;
|
|
369
|
+
}
|
|
370
|
+
if (this.drainResolve) {
|
|
371
|
+
this.drainResolve();
|
|
372
|
+
this.drainResolve = null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
shouldIgnoreAgentStderr(message) {
|
|
376
|
+
return (message.includes("Error handling notification") &&
|
|
377
|
+
message.includes("method: 'session/update'") &&
|
|
378
|
+
message.includes("message: 'Invalid params'")) || isMissingPostToolHookMessage(message) || message.includes("<local-command-stdout>");
|
|
379
|
+
}
|
|
380
|
+
shouldIgnoreToolOutputLog(message) {
|
|
381
|
+
const normalized = message.trim();
|
|
382
|
+
return normalized.startsWith('[{"index":') || normalized.startsWith("[{\"index\":");
|
|
383
|
+
}
|
|
384
|
+
async handlePermissionRequest(params) {
|
|
385
|
+
const requestId = randomUUID();
|
|
386
|
+
this.onEvent({
|
|
387
|
+
type: "permission_requested",
|
|
388
|
+
payload: {
|
|
389
|
+
requestId,
|
|
390
|
+
toolCall: params.toolCall,
|
|
391
|
+
options: params.options,
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
return await new Promise((resolve, reject) => {
|
|
395
|
+
this.pendingPermissions.set(requestId, { resolve, reject });
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
rejectPendingPermissions(reason) {
|
|
399
|
+
for (const pending of this.pendingPermissions.values()) {
|
|
400
|
+
pending.reject(reason);
|
|
401
|
+
}
|
|
402
|
+
this.pendingPermissions.clear();
|
|
403
|
+
}
|
|
404
|
+
rejectPendingQuestions(reason) {
|
|
405
|
+
for (const pending of this.pendingQuestions.values()) {
|
|
406
|
+
pending.reject(reason);
|
|
407
|
+
}
|
|
408
|
+
this.pendingQuestions.clear();
|
|
409
|
+
}
|
|
410
|
+
async handleExtMethod(method, params) {
|
|
411
|
+
if (method === "leduo/ask_question") {
|
|
412
|
+
return await this.handleAskQuestion(params);
|
|
413
|
+
}
|
|
414
|
+
throw new Error(`Unknown extension method: ${method}`);
|
|
415
|
+
}
|
|
416
|
+
async handleAskQuestion(params) {
|
|
417
|
+
const questionId = randomUUID();
|
|
418
|
+
const question = typeof params.question === "string" ? params.question : "";
|
|
419
|
+
const rawOptions = Array.isArray(params.options) ? params.options : [];
|
|
420
|
+
const options = rawOptions
|
|
421
|
+
.map((opt) => {
|
|
422
|
+
if (opt && typeof opt === "object" && !Array.isArray(opt)) {
|
|
423
|
+
const record = opt;
|
|
424
|
+
return {
|
|
425
|
+
id: typeof record.id === "string" ? record.id : "",
|
|
426
|
+
label: typeof record.label === "string" ? record.label : "",
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
})
|
|
431
|
+
.filter((opt) => opt !== null && opt.id !== "" && opt.label !== "");
|
|
432
|
+
const allowCustomAnswer = params.allowCustomAnswer === true;
|
|
433
|
+
this.onEvent({
|
|
434
|
+
type: "question_requested",
|
|
435
|
+
payload: { questionId, question, options, allowCustomAnswer },
|
|
436
|
+
});
|
|
437
|
+
return await new Promise((resolve, reject) => {
|
|
438
|
+
this.pendingQuestions.set(questionId, { resolve, reject });
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
resolveWorkspacePath(targetPath) {
|
|
442
|
+
const absolutePath = path.resolve(this.workspacePath, targetPath);
|
|
443
|
+
const relativePath = path.relative(this.workspacePath, absolutePath);
|
|
444
|
+
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
445
|
+
throw new Error(`Refusing to access file outside workspace: ${targetPath}`);
|
|
446
|
+
}
|
|
447
|
+
return absolutePath;
|
|
448
|
+
}
|
|
449
|
+
async handleReadTextFile(params) {
|
|
450
|
+
const filePath = path.isAbsolute(params.path)
|
|
451
|
+
? params.path
|
|
452
|
+
: this.resolveWorkspacePath(params.path);
|
|
453
|
+
const content = await readFile(filePath, "utf8");
|
|
454
|
+
if (params.line != null || params.limit != null) {
|
|
455
|
+
const lines = content.split("\n");
|
|
456
|
+
const offset = (params.line ?? 1) - 1;
|
|
457
|
+
const limit = params.limit ?? lines.length;
|
|
458
|
+
const start = Math.max(0, offset);
|
|
459
|
+
const end = Math.min(lines.length, start + limit);
|
|
460
|
+
return { content: lines.slice(start, end).join("\n") };
|
|
461
|
+
}
|
|
462
|
+
return { content };
|
|
463
|
+
}
|
|
464
|
+
async handleWriteTextFile(params) {
|
|
465
|
+
const filePath = path.isAbsolute(params.path)
|
|
466
|
+
? params.path
|
|
467
|
+
: this.resolveWorkspacePath(params.path);
|
|
468
|
+
const dirName = path.dirname(filePath);
|
|
469
|
+
await mkdir(dirName, { recursive: true });
|
|
470
|
+
await writeFile(filePath, params.content, "utf8");
|
|
471
|
+
return {};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function isMissingPostToolHookMessage(message) {
|
|
475
|
+
return message.includes("No onPostToolUseHook found for tool use ID:");
|
|
476
|
+
}
|
|
@@ -10,6 +10,15 @@ const POLL_INTERVAL_MS = 2000;
|
|
|
10
10
|
const ACTIVITY_TYPES = new Set(["assistant", "user", "progress"]);
|
|
11
11
|
/** Types to skip when scanning for the last meaningful entry. */
|
|
12
12
|
const SKIP_TYPES = new Set(["last-prompt", "system", "file-history-snapshot", "queue-operation"]);
|
|
13
|
+
function extractAssistantContentTypes(entry) {
|
|
14
|
+
const content = entry.message?.content;
|
|
15
|
+
if (!Array.isArray(content)) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return content
|
|
19
|
+
.map((block) => (block && typeof block === "object" ? block.type : undefined))
|
|
20
|
+
.filter((type) => typeof type === "string");
|
|
21
|
+
}
|
|
13
22
|
/**
|
|
14
23
|
* Detect if a user entry represents a local CLI command (not a real user message to Claude).
|
|
15
24
|
* Local commands have content containing `<command-name>`, `<local-command-stdout>`, or `<local-command-caveat>`.
|
|
@@ -58,9 +67,10 @@ export function detectClearCommand(entry) {
|
|
|
58
67
|
* Given a parsed JSONL entry, return the activity state.
|
|
59
68
|
*
|
|
60
69
|
* Rules:
|
|
61
|
-
* assistant +
|
|
62
|
-
* assistant +
|
|
63
|
-
* assistant +
|
|
70
|
+
* assistant + tool_use content / stop_reason → pending
|
|
71
|
+
* assistant + thinking content only → running
|
|
72
|
+
* assistant + text content only → completed
|
|
73
|
+
* assistant + explicit terminal stop_reason → completed
|
|
64
74
|
* user (local command / meta) → completed
|
|
65
75
|
* user (real message) → running
|
|
66
76
|
* system + subtype "local_command" → completed
|
|
@@ -70,13 +80,18 @@ export function detectClearCommand(entry) {
|
|
|
70
80
|
export function determineActivityState(entry) {
|
|
71
81
|
const { type } = entry;
|
|
72
82
|
if (type === "assistant") {
|
|
83
|
+
const contentTypes = extractAssistantContentTypes(entry);
|
|
73
84
|
const stopReason = entry.message?.stop_reason;
|
|
74
|
-
if (stopReason
|
|
75
|
-
return "running";
|
|
76
|
-
if (stopReason === "tool_use")
|
|
85
|
+
if (stopReason === "tool_use" || contentTypes.includes("tool_use"))
|
|
77
86
|
return "pending";
|
|
87
|
+
if (stopReason != null)
|
|
88
|
+
return "completed";
|
|
89
|
+
if (contentTypes.includes("thinking"))
|
|
90
|
+
return "running";
|
|
91
|
+
if (contentTypes.includes("text"))
|
|
92
|
+
return "completed";
|
|
78
93
|
// end_turn, stop_sequence, max_tokens, etc. – treat as completed
|
|
79
|
-
return "
|
|
94
|
+
return "running";
|
|
80
95
|
}
|
|
81
96
|
if (type === "user") {
|
|
82
97
|
// Local CLI commands (/mcp, /status, /clear, etc.) are already finished
|
package/dist/server/index.js
CHANGED
|
@@ -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";
|
|
@@ -35,6 +36,7 @@ const listenHost = bindMode === "local" ? "127.0.0.1" : "0.0.0.0";
|
|
|
35
36
|
const launchHost = bindMode === "local" ? "127.0.0.1" : pickPreferredLanIp();
|
|
36
37
|
const launchUser = userInfo().username;
|
|
37
38
|
const claudeBin = process.env.LEDUO_PATROL_CLAUDE_BIN?.trim() || undefined;
|
|
39
|
+
const agentBinPath = resolveAgentBinPath();
|
|
38
40
|
const accessKey = await resolveAccessKey();
|
|
39
41
|
const enableShell = parseBooleanFlag(process.env.LEDUO_ENABLE_SHELL, true);
|
|
40
42
|
const allowSkipPermissions = process.env.LEDUO_PATROL_ALLOW_SKIP_PERMISSIONS === "true";
|
|
@@ -44,6 +46,7 @@ const wss = new WebSocketServer({ server, path: "/ws" });
|
|
|
44
46
|
const sessionManager = new SessionManager({
|
|
45
47
|
allowedRoots,
|
|
46
48
|
claudeBin,
|
|
49
|
+
agentBinPath,
|
|
47
50
|
allowSkipPermissions,
|
|
48
51
|
});
|
|
49
52
|
await sessionManager.initialize();
|
|
@@ -87,11 +90,29 @@ app.get("/api/config", (_req, res) => {
|
|
|
87
90
|
launchHost,
|
|
88
91
|
launchUser,
|
|
89
92
|
allowSkipPermissions,
|
|
93
|
+
availableSessionEngines: sessionManager.getAvailableEngines(),
|
|
94
|
+
defaultSessionEngine: "cli",
|
|
90
95
|
});
|
|
91
96
|
});
|
|
92
97
|
app.get("/api/state", (_req, res) => {
|
|
93
98
|
res.json(sessionManager.getStateSnapshot());
|
|
94
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
|
+
});
|
|
95
116
|
app.get("/api/session-diff/files", async (req, res) => {
|
|
96
117
|
try {
|
|
97
118
|
const clientSessionId = typeof req.query.clientSessionId === "string" ? req.query.clientSessionId : "";
|
|
@@ -197,7 +218,25 @@ wss.on("connection", (socket, request) => {
|
|
|
197
218
|
});
|
|
198
219
|
break;
|
|
199
220
|
case "create_session":
|
|
200
|
-
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);
|
|
201
240
|
break;
|
|
202
241
|
case "close_session":
|
|
203
242
|
await sessionManager.closeSession(message.payload.clientSessionId);
|
|
@@ -331,3 +370,17 @@ function parseBooleanFlag(rawValue, defaultValue) {
|
|
|
331
370
|
}
|
|
332
371
|
return defaultValue;
|
|
333
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
|
+
}
|