@wrongstack/acp 1.0.24 → 1.0.27
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/agent.js +12 -3
- package/dist/client/acp-session-auth.d.ts +20 -0
- package/dist/client/acp-session.d.ts +1 -0
- package/dist/client.js +474 -406
- package/dist/index.js +475 -408
- package/dist/wrongstack-acp-agent.js +12 -3
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -991,6 +991,16 @@ import {
|
|
|
991
991
|
var DEFAULT_MAX_FRAME_CHARS = 20 * 1024 * 1024;
|
|
992
992
|
var DEFAULT_MAX_QUEUED_MESSAGES = 1e3;
|
|
993
993
|
var DEFAULT_MAX_QUEUED_CHARS = 32 * 1024 * 1024;
|
|
994
|
+
function takeLines(pending, chunk) {
|
|
995
|
+
const lines = [];
|
|
996
|
+
let start = 0;
|
|
997
|
+
for (let nl = chunk.indexOf("\n"); nl !== -1; nl = chunk.indexOf("\n", start)) {
|
|
998
|
+
lines.push(pending + chunk.slice(start, nl));
|
|
999
|
+
pending = "";
|
|
1000
|
+
start = nl + 1;
|
|
1001
|
+
}
|
|
1002
|
+
return { lines, pending: pending + chunk.slice(start) };
|
|
1003
|
+
}
|
|
994
1004
|
function positiveLimit(value, fallback) {
|
|
995
1005
|
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
996
1006
|
}
|
|
@@ -1078,9 +1088,8 @@ var StdioTransport = class {
|
|
|
1078
1088
|
this.claimHandlers.clear();
|
|
1079
1089
|
}
|
|
1080
1090
|
onData(chunk) {
|
|
1081
|
-
this.buffer
|
|
1082
|
-
|
|
1083
|
-
this.buffer = lines.pop() ?? "";
|
|
1091
|
+
const { lines, pending } = takeLines(this.buffer, chunk);
|
|
1092
|
+
this.buffer = pending;
|
|
1084
1093
|
if (this.buffer.length > this.maxFrameChars) {
|
|
1085
1094
|
this.stderr.write(
|
|
1086
1095
|
`[wstack-acp frame error] pending frame exceeds ${this.maxFrameChars} characters
|
|
@@ -1320,9 +1329,8 @@ var ClientTransport = class {
|
|
|
1320
1329
|
this.child = null;
|
|
1321
1330
|
}
|
|
1322
1331
|
onChildData(chunk) {
|
|
1323
|
-
this.buffer
|
|
1324
|
-
|
|
1325
|
-
this.buffer = lines.pop() ?? "";
|
|
1332
|
+
const { lines, pending } = takeLines(this.buffer, chunk);
|
|
1333
|
+
this.buffer = pending;
|
|
1326
1334
|
if (this.buffer.length > this.maxFrameChars) {
|
|
1327
1335
|
writeErr(`[acp-child pending frame exceeds ${this.maxFrameChars} characters]
|
|
1328
1336
|
`);
|
|
@@ -1858,6 +1866,324 @@ function isBestEffortAckMethod(method) {
|
|
|
1858
1866
|
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
1859
1867
|
}
|
|
1860
1868
|
|
|
1869
|
+
// src/client/acp-session-errors.ts
|
|
1870
|
+
var ACPSessionError = class extends Error {
|
|
1871
|
+
kind;
|
|
1872
|
+
cause;
|
|
1873
|
+
constructor(kind, message, cause) {
|
|
1874
|
+
super(message);
|
|
1875
|
+
this.name = "ACPSessionError";
|
|
1876
|
+
this.kind = kind;
|
|
1877
|
+
this.cause = cause;
|
|
1878
|
+
}
|
|
1879
|
+
};
|
|
1880
|
+
function isJsonRpcError(v) {
|
|
1881
|
+
return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
|
|
1882
|
+
}
|
|
1883
|
+
function isAuthRequiredError(err) {
|
|
1884
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1885
|
+
if (/auth(_|-)?required|authentication required/i.test(message)) return true;
|
|
1886
|
+
const cause = err instanceof ACPSessionError ? err.cause : err && typeof err === "object" && "cause" in err ? err.cause : err;
|
|
1887
|
+
if (!cause || typeof cause !== "object") return false;
|
|
1888
|
+
const data = cause.data;
|
|
1889
|
+
if (data === "auth_required" || data === "AUTH_REQUIRED") return true;
|
|
1890
|
+
if (data && typeof data === "object") {
|
|
1891
|
+
const d = data;
|
|
1892
|
+
if (d.authRequired === true) return true;
|
|
1893
|
+
if (d.code === "auth_required" || d.code === "AUTH_REQUIRED") return true;
|
|
1894
|
+
}
|
|
1895
|
+
return false;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
// src/client/acp-session-ops.ts
|
|
1899
|
+
function filterMcpServers(agentCapabilities, servers) {
|
|
1900
|
+
if (!servers || servers.length === 0) return [];
|
|
1901
|
+
const mcpCaps = agentCapabilities.mcpCapabilities ?? {};
|
|
1902
|
+
return servers.filter((s) => {
|
|
1903
|
+
if ("type" in s && s.type === "http") return mcpCaps.http === true;
|
|
1904
|
+
if ("type" in s && s.type === "sse") return mcpCaps.sse === true;
|
|
1905
|
+
return true;
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
async function executeLoadSession(ctx, sessionId, mcpServers, cwd) {
|
|
1909
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
1910
|
+
if (!ctx.agentCapabilities.loadSession) {
|
|
1911
|
+
throw new ACPSessionError(
|
|
1912
|
+
"unsupported_capability",
|
|
1913
|
+
"agent does not support session/load (loadSession capability not advertised)"
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
1917
|
+
await ctx.closeSession();
|
|
1918
|
+
}
|
|
1919
|
+
ctx.resetScratch();
|
|
1920
|
+
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
1921
|
+
const id = ctx.allocId();
|
|
1922
|
+
const result = await ctx.sendRequest(id, "session/load", {
|
|
1923
|
+
sessionId,
|
|
1924
|
+
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
1925
|
+
mcpServers: servers
|
|
1926
|
+
});
|
|
1927
|
+
if (isJsonRpcError(result)) {
|
|
1928
|
+
throw new ACPSessionError("prompt_failed", `session/load failed: ${result.message}`, result);
|
|
1929
|
+
}
|
|
1930
|
+
ctx.setSessionId(sessionId);
|
|
1931
|
+
}
|
|
1932
|
+
async function executeResumeSession(ctx, sessionId, mcpServers, cwd) {
|
|
1933
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
1934
|
+
if (!ctx.agentCapabilities.sessionCapabilities?.resume) {
|
|
1935
|
+
throw new ACPSessionError(
|
|
1936
|
+
"unsupported_capability",
|
|
1937
|
+
"agent does not support session/resume (sessionCapabilities.resume not advertised)"
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
1941
|
+
await ctx.closeSession();
|
|
1942
|
+
}
|
|
1943
|
+
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
1944
|
+
const id = ctx.allocId();
|
|
1945
|
+
const result = await ctx.sendRequest(id, "session/resume", {
|
|
1946
|
+
sessionId,
|
|
1947
|
+
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
1948
|
+
mcpServers: servers
|
|
1949
|
+
});
|
|
1950
|
+
if (isJsonRpcError(result)) {
|
|
1951
|
+
throw new ACPSessionError("prompt_failed", `session/resume failed: ${result.message}`, result);
|
|
1952
|
+
}
|
|
1953
|
+
ctx.setSessionId(sessionId);
|
|
1954
|
+
}
|
|
1955
|
+
async function executeListSessions(ctx, cursor, cwd) {
|
|
1956
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
1957
|
+
if (!ctx.agentCapabilities.sessionCapabilities?.list) {
|
|
1958
|
+
throw new ACPSessionError(
|
|
1959
|
+
"unsupported_capability",
|
|
1960
|
+
"agent does not support session/list (sessionCapabilities.list not advertised)"
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
const id = ctx.allocId();
|
|
1964
|
+
const params = {};
|
|
1965
|
+
if (cursor !== void 0) params.cursor = cursor;
|
|
1966
|
+
if (cwd !== void 0) params.cwd = cwd;
|
|
1967
|
+
const result = await ctx.sendRequest(id, "session/list", params);
|
|
1968
|
+
if (isJsonRpcError(result)) {
|
|
1969
|
+
throw new ACPSessionError("prompt_failed", `session/list failed: ${result.message}`, result);
|
|
1970
|
+
}
|
|
1971
|
+
const r = result;
|
|
1972
|
+
return {
|
|
1973
|
+
sessions: r.sessions ?? [],
|
|
1974
|
+
nextCursor: r.nextCursor
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
async function executeDeleteSession(ctx, sessionId) {
|
|
1978
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
1979
|
+
if (!ctx.agentCapabilities.sessionCapabilities?.delete) {
|
|
1980
|
+
throw new ACPSessionError(
|
|
1981
|
+
"unsupported_capability",
|
|
1982
|
+
"agent does not support session/delete (sessionCapabilities.delete not advertised)"
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
const id = ctx.allocId();
|
|
1986
|
+
const result = await ctx.sendRequest(id, "session/delete", { sessionId });
|
|
1987
|
+
if (isJsonRpcError(result)) {
|
|
1988
|
+
throw new ACPSessionError("prompt_failed", `session/delete failed: ${result.message}`, result);
|
|
1989
|
+
}
|
|
1990
|
+
if (ctx.sessionId === sessionId) {
|
|
1991
|
+
ctx.setSessionId(null);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
async function executeForkSession(ctx, sourceSessionId, cwd, mcpServers) {
|
|
1995
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
1996
|
+
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
1997
|
+
const id = ctx.allocId();
|
|
1998
|
+
const result = await ctx.sendRequest(id, "session/fork", {
|
|
1999
|
+
sessionId: sourceSessionId,
|
|
2000
|
+
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
2001
|
+
...servers.length > 0 ? { mcpServers: servers } : {}
|
|
2002
|
+
});
|
|
2003
|
+
if (isJsonRpcError(result)) {
|
|
2004
|
+
throw new ACPSessionError("prompt_failed", `session/fork failed: ${result.message}`, result);
|
|
2005
|
+
}
|
|
2006
|
+
const newId = result.sessionId;
|
|
2007
|
+
if (typeof newId !== "string" || !newId) {
|
|
2008
|
+
throw new ACPSessionError("protocol_error", "session/fork returned no sessionId", result);
|
|
2009
|
+
}
|
|
2010
|
+
return newId;
|
|
2011
|
+
}
|
|
2012
|
+
async function executeSetMode(ctx, sessionId, modeId) {
|
|
2013
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2014
|
+
const id = ctx.allocId();
|
|
2015
|
+
const result = await ctx.sendRequest(id, "session/set_mode", { sessionId, modeId });
|
|
2016
|
+
if (isJsonRpcError(result)) {
|
|
2017
|
+
throw new ACPSessionError(
|
|
2018
|
+
"prompt_failed",
|
|
2019
|
+
`session/set_mode failed: ${result.message}`,
|
|
2020
|
+
result
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
async function executeSetConfigOption(ctx, sessionId, configId, value) {
|
|
2025
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2026
|
+
const id = ctx.allocId();
|
|
2027
|
+
const result = await ctx.sendRequest(id, "session/set_config_option", {
|
|
2028
|
+
sessionId,
|
|
2029
|
+
configId,
|
|
2030
|
+
value
|
|
2031
|
+
});
|
|
2032
|
+
if (isJsonRpcError(result)) {
|
|
2033
|
+
throw new ACPSessionError(
|
|
2034
|
+
"prompt_failed",
|
|
2035
|
+
`session/set_config_option failed: ${result.message}`,
|
|
2036
|
+
result
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
async function executeListProviders(ctx) {
|
|
2041
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2042
|
+
const id = ctx.allocId();
|
|
2043
|
+
const result = await ctx.sendRequest(id, "providers/list", {});
|
|
2044
|
+
if (isJsonRpcError(result)) {
|
|
2045
|
+
throw new ACPSessionError("prompt_failed", `providers/list failed: ${result.message}`, result);
|
|
2046
|
+
}
|
|
2047
|
+
const r = result;
|
|
2048
|
+
return { providers: r.providers ?? [], currentProviderId: r.currentProviderId ?? null };
|
|
2049
|
+
}
|
|
2050
|
+
async function executeSetProvider(ctx, providerId, config) {
|
|
2051
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2052
|
+
const id = ctx.allocId();
|
|
2053
|
+
const result = await ctx.sendRequest(id, "providers/set", { providerId, ...config ?? {} });
|
|
2054
|
+
if (isJsonRpcError(result)) {
|
|
2055
|
+
throw new ACPSessionError("prompt_failed", `providers/set failed: ${result.message}`, result);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
async function executeDisableProvider(ctx) {
|
|
2059
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2060
|
+
const id = ctx.allocId();
|
|
2061
|
+
const result = await ctx.sendRequest(id, "providers/disable", {});
|
|
2062
|
+
if (isJsonRpcError(result)) {
|
|
2063
|
+
throw new ACPSessionError(
|
|
2064
|
+
"prompt_failed",
|
|
2065
|
+
`providers/disable failed: ${result.message}`,
|
|
2066
|
+
result
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
async function executeMcpMessage(ctx, connectionId, message) {
|
|
2071
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2072
|
+
const id = ctx.allocId();
|
|
2073
|
+
const result = await ctx.sendRequest(id, "mcp/message", { connectionId, message });
|
|
2074
|
+
if (isJsonRpcError(result)) {
|
|
2075
|
+
throw new ACPSessionError("prompt_failed", `mcp/message failed: ${result.message}`, result);
|
|
2076
|
+
}
|
|
2077
|
+
return result;
|
|
2078
|
+
}
|
|
2079
|
+
async function executeCreateSession(ctx) {
|
|
2080
|
+
const servers = filterMcpServers(ctx.agentCapabilities, ctx.opts.mcpServers);
|
|
2081
|
+
const id = ctx.allocId();
|
|
2082
|
+
const result = await ctx.sendRequest(id, "session/new", {
|
|
2083
|
+
cwd: ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
2084
|
+
mcpServers: servers
|
|
2085
|
+
});
|
|
2086
|
+
if (isJsonRpcError(result)) {
|
|
2087
|
+
throw new ACPSessionError(
|
|
2088
|
+
"session_create_failed",
|
|
2089
|
+
`session/new failed: ${result.message}`,
|
|
2090
|
+
result
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2093
|
+
const sessionId = result.sessionId;
|
|
2094
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
2095
|
+
throw new ACPSessionError("protocol_error", "session/new returned no sessionId", result);
|
|
2096
|
+
}
|
|
2097
|
+
return sessionId;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// src/client/acp-session-auth.ts
|
|
2101
|
+
async function authenticate(host, methodId) {
|
|
2102
|
+
if (host.state === "closed") {
|
|
2103
|
+
throw new ACPSessionError("closed", "session is closed");
|
|
2104
|
+
}
|
|
2105
|
+
if (host.state !== "ready" && host.state !== "authenticated") {
|
|
2106
|
+
throw new ACPSessionError(
|
|
2107
|
+
"protocol_error",
|
|
2108
|
+
`authenticate called in state=${host.state} (expected 'ready')`
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
if (host.state === "authenticated") return;
|
|
2112
|
+
if (!host.authMethods.some((m) => m.id === methodId)) {
|
|
2113
|
+
throw new ACPSessionError(
|
|
2114
|
+
"auth_failed",
|
|
2115
|
+
`auth method "${methodId}" not in advertised methods: ${host.authMethods.map((m) => m.id).join(", ")}`
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
if (host.authMethods.find((m) => m.id === methodId)?.type === "terminal") {
|
|
2119
|
+
throw new ACPSessionError(
|
|
2120
|
+
"auth_failed",
|
|
2121
|
+
"Terminal authentication requires interactive login followed by reconnect; it cannot use authenticate"
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
const id = host.allocId();
|
|
2125
|
+
const result = await host.sendRequest(id, "authenticate", { methodId });
|
|
2126
|
+
if (isJsonRpcError(result)) {
|
|
2127
|
+
throw new ACPSessionError("auth_failed", `authenticate failed: ${result.message}`, result);
|
|
2128
|
+
}
|
|
2129
|
+
host.state = "authenticated";
|
|
2130
|
+
}
|
|
2131
|
+
async function logout(host) {
|
|
2132
|
+
if (host.state === "closed") {
|
|
2133
|
+
throw new ACPSessionError("closed", "session is closed");
|
|
2134
|
+
}
|
|
2135
|
+
if (!host.agentCapabilities.auth?.logout) {
|
|
2136
|
+
throw new ACPSessionError(
|
|
2137
|
+
"unsupported_capability",
|
|
2138
|
+
"agent does not support logout (auth.logout capability not advertised)"
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
const id = host.allocId();
|
|
2142
|
+
const result = await host.sendRequest(id, "logout", {});
|
|
2143
|
+
if (isJsonRpcError(result)) {
|
|
2144
|
+
throw new ACPSessionError("logout_failed", `logout failed: ${result.message}`, result);
|
|
2145
|
+
}
|
|
2146
|
+
host.state = "ready";
|
|
2147
|
+
}
|
|
2148
|
+
async function createSessionWithAuth(host) {
|
|
2149
|
+
try {
|
|
2150
|
+
return await executeCreateSession(host.opContext());
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
if (host.state === "authenticated" || !isAuthRequiredError(err)) {
|
|
2153
|
+
throw err instanceof ACPSessionError ? err : new ACPSessionError(
|
|
2154
|
+
"session_create_failed",
|
|
2155
|
+
err instanceof Error ? err.message : String(err),
|
|
2156
|
+
err
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
await host.ensureAuthenticated();
|
|
2160
|
+
return executeCreateSession(host.opContext());
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
async function ensureAuthenticated(host) {
|
|
2164
|
+
if (host.state === "authenticated") return;
|
|
2165
|
+
if (host.authMethods.length === 0) {
|
|
2166
|
+
throw new ACPSessionError(
|
|
2167
|
+
"auth_failed",
|
|
2168
|
+
"This agent requires authentication before a session can start, but advertised no authMethods. Log into the CLI, then retry."
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
const inProcess = host.authMethods.find(
|
|
2172
|
+
(m) => m.type === void 0 || m.type === "agent" || m.type === "oauth" || m.type === "http"
|
|
2173
|
+
);
|
|
2174
|
+
if (inProcess) {
|
|
2175
|
+
await host.authenticate(inProcess.id);
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
const terminal = host.authMethods.find((m) => m.type === "terminal");
|
|
2179
|
+
const setupArgs = terminal?.args?.length ? terminal.args.join(" ") : void 0;
|
|
2180
|
+
const setup = setupArgs !== void 0 ? `${host.opts.command}${host.opts.args?.length ? ` ${host.opts.args.join(" ")}` : ""} ${setupArgs}` : `${host.opts.command}${host.opts.args?.length ? ` ${host.opts.args.join(" ")}` : ""}`;
|
|
2181
|
+
throw new ACPSessionError(
|
|
2182
|
+
"auth_failed",
|
|
2183
|
+
`This agent requires a terminal login before ACP can start. Run \`${setup}\` (or the CLI's /login), then retry.`
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
|
|
1861
2187
|
// src/client/file-server.ts
|
|
1862
2188
|
import { randomBytes } from "node:crypto";
|
|
1863
2189
|
import { realpathSync } from "node:fs";
|
|
@@ -2222,344 +2548,113 @@ async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, s
|
|
|
2222
2548
|
await sender.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
|
|
2223
2549
|
}
|
|
2224
2550
|
} catch (err) {
|
|
2225
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2226
|
-
await sender.sendErrorResponse(id, -32603, message);
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
async function authorizeAcpCallback(permissionPolicy, partial, callbackOptions) {
|
|
2230
|
-
try {
|
|
2231
|
-
const outcome = await runPermissionWithDeadline(
|
|
2232
|
-
permissionPolicy,
|
|
2233
|
-
{
|
|
2234
|
-
toolCall: {
|
|
2235
|
-
sessionUpdate: "tool_call_update",
|
|
2236
|
-
toolCallId: partial.toolCallId,
|
|
2237
|
-
title: partial.title,
|
|
2238
|
-
kind: partial.kind,
|
|
2239
|
-
status: "pending",
|
|
2240
|
-
rawInput: partial.rawInput
|
|
2241
|
-
},
|
|
2242
|
-
options: [
|
|
2243
|
-
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
2244
|
-
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
2245
|
-
]
|
|
2246
|
-
},
|
|
2247
|
-
callbackOptions
|
|
2248
|
-
);
|
|
2249
|
-
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always" ? "allowed" : "denied";
|
|
2250
|
-
} catch (err) {
|
|
2251
|
-
return isAbortLikeError(err) ? "cancelled" : "denied";
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
async function runPermissionWithDeadline(permissionPolicy, call, callbackOptions) {
|
|
2255
|
-
const timeoutMs = resolvePermissionDeadline(callbackOptions.permissionTimeoutMs);
|
|
2256
|
-
const controller = new AbortController();
|
|
2257
|
-
const abort = () => controller.abort();
|
|
2258
|
-
const timer = timeoutMs === null ? null : setTimeout(abort, timeoutMs);
|
|
2259
|
-
let removeAbort;
|
|
2260
|
-
if (callbackOptions.signal) {
|
|
2261
|
-
if (callbackOptions.signal.aborted) {
|
|
2262
|
-
abort();
|
|
2263
|
-
} else {
|
|
2264
|
-
callbackOptions.signal.addEventListener("abort", abort, { once: true });
|
|
2265
|
-
removeAbort = () => callbackOptions.signal?.removeEventListener("abort", abort);
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
try {
|
|
2269
|
-
return await Promise.race([
|
|
2270
|
-
permissionPolicy({
|
|
2271
|
-
toolCall: call.toolCall,
|
|
2272
|
-
options: call.options,
|
|
2273
|
-
signal: controller.signal
|
|
2274
|
-
}),
|
|
2275
|
-
rejectOnAbort(controller.signal)
|
|
2276
|
-
]);
|
|
2277
|
-
} finally {
|
|
2278
|
-
if (timer !== null) clearTimeout(timer);
|
|
2279
|
-
removeAbort?.();
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
function rejectOnAbort(signal) {
|
|
2283
|
-
return new Promise((_, reject) => {
|
|
2284
|
-
const rejectAbort = () => reject(new Error("permission request cancelled or timed out"));
|
|
2285
|
-
if (signal.aborted) {
|
|
2286
|
-
rejectAbort();
|
|
2287
|
-
return;
|
|
2288
|
-
}
|
|
2289
|
-
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
2290
|
-
});
|
|
2291
|
-
}
|
|
2292
|
-
function isAbortLikeError(err) {
|
|
2293
|
-
return err instanceof Error && /cancelled|canceled|timed out|aborted/i.test(err.message);
|
|
2294
|
-
}
|
|
2295
|
-
function resolvePermissionDeadline(value) {
|
|
2296
|
-
if (value === Number.POSITIVE_INFINITY) return null;
|
|
2297
|
-
if (value !== void 0 && Number.isFinite(value) && value > 0) return Math.trunc(value);
|
|
2298
|
-
return DEFAULT_PERMISSION_TIMEOUT_MS;
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
// src/client/acp-session-content.ts
|
|
2302
|
-
function textContent(text) {
|
|
2303
|
-
return { type: "text", text };
|
|
2304
|
-
}
|
|
2305
|
-
function imageContent(mimeType, data) {
|
|
2306
|
-
return { type: "image", mimeType, data };
|
|
2307
|
-
}
|
|
2308
|
-
function audioContent(mimeType, data) {
|
|
2309
|
-
return { type: "audio", mimeType, data };
|
|
2310
|
-
}
|
|
2311
|
-
function extractText(block) {
|
|
2312
|
-
if (typeof block !== "object" || block === null) return "";
|
|
2313
|
-
const b = block;
|
|
2314
|
-
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
2315
|
-
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
2316
|
-
return b.resource.text;
|
|
2317
|
-
}
|
|
2318
|
-
return "";
|
|
2319
|
-
}
|
|
2320
|
-
function isRecord(v) {
|
|
2321
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2322
|
-
}
|
|
2323
|
-
function emptyRunResult(stopReason) {
|
|
2324
|
-
return {
|
|
2325
|
-
text: "",
|
|
2326
|
-
stopReason,
|
|
2327
|
-
hasText: false,
|
|
2328
|
-
toolCalls: [],
|
|
2329
|
-
diffs: [],
|
|
2330
|
-
thoughts: ""
|
|
2331
|
-
};
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
// src/client/acp-session-errors.ts
|
|
2335
|
-
var ACPSessionError = class extends Error {
|
|
2336
|
-
kind;
|
|
2337
|
-
cause;
|
|
2338
|
-
constructor(kind, message, cause) {
|
|
2339
|
-
super(message);
|
|
2340
|
-
this.name = "ACPSessionError";
|
|
2341
|
-
this.kind = kind;
|
|
2342
|
-
this.cause = cause;
|
|
2343
|
-
}
|
|
2344
|
-
};
|
|
2345
|
-
function isJsonRpcError(v) {
|
|
2346
|
-
return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
|
|
2347
|
-
}
|
|
2348
|
-
function isAuthRequiredError(err) {
|
|
2349
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2350
|
-
if (/auth(_|-)?required|authentication required/i.test(message)) return true;
|
|
2351
|
-
const cause = err instanceof ACPSessionError ? err.cause : err && typeof err === "object" && "cause" in err ? err.cause : err;
|
|
2352
|
-
if (!cause || typeof cause !== "object") return false;
|
|
2353
|
-
const data = cause.data;
|
|
2354
|
-
if (data === "auth_required" || data === "AUTH_REQUIRED") return true;
|
|
2355
|
-
if (data && typeof data === "object") {
|
|
2356
|
-
const d = data;
|
|
2357
|
-
if (d.authRequired === true) return true;
|
|
2358
|
-
if (d.code === "auth_required" || d.code === "AUTH_REQUIRED") return true;
|
|
2359
|
-
}
|
|
2360
|
-
return false;
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
// src/client/acp-session-ops.ts
|
|
2364
|
-
function filterMcpServers(agentCapabilities, servers) {
|
|
2365
|
-
if (!servers || servers.length === 0) return [];
|
|
2366
|
-
const mcpCaps = agentCapabilities.mcpCapabilities ?? {};
|
|
2367
|
-
return servers.filter((s) => {
|
|
2368
|
-
if ("type" in s && s.type === "http") return mcpCaps.http === true;
|
|
2369
|
-
if ("type" in s && s.type === "sse") return mcpCaps.sse === true;
|
|
2370
|
-
return true;
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
async function executeLoadSession(ctx, sessionId, mcpServers, cwd) {
|
|
2374
|
-
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2375
|
-
if (!ctx.agentCapabilities.loadSession) {
|
|
2376
|
-
throw new ACPSessionError(
|
|
2377
|
-
"unsupported_capability",
|
|
2378
|
-
"agent does not support session/load (loadSession capability not advertised)"
|
|
2379
|
-
);
|
|
2380
|
-
}
|
|
2381
|
-
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
2382
|
-
await ctx.closeSession();
|
|
2383
|
-
}
|
|
2384
|
-
ctx.resetScratch();
|
|
2385
|
-
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
2386
|
-
const id = ctx.allocId();
|
|
2387
|
-
const result = await ctx.sendRequest(id, "session/load", {
|
|
2388
|
-
sessionId,
|
|
2389
|
-
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
2390
|
-
mcpServers: servers
|
|
2391
|
-
});
|
|
2392
|
-
if (isJsonRpcError(result)) {
|
|
2393
|
-
throw new ACPSessionError("prompt_failed", `session/load failed: ${result.message}`, result);
|
|
2394
|
-
}
|
|
2395
|
-
ctx.setSessionId(sessionId);
|
|
2396
|
-
}
|
|
2397
|
-
async function executeResumeSession(ctx, sessionId, mcpServers, cwd) {
|
|
2398
|
-
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2399
|
-
if (!ctx.agentCapabilities.sessionCapabilities?.resume) {
|
|
2400
|
-
throw new ACPSessionError(
|
|
2401
|
-
"unsupported_capability",
|
|
2402
|
-
"agent does not support session/resume (sessionCapabilities.resume not advertised)"
|
|
2403
|
-
);
|
|
2404
|
-
}
|
|
2405
|
-
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
2406
|
-
await ctx.closeSession();
|
|
2407
|
-
}
|
|
2408
|
-
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
2409
|
-
const id = ctx.allocId();
|
|
2410
|
-
const result = await ctx.sendRequest(id, "session/resume", {
|
|
2411
|
-
sessionId,
|
|
2412
|
-
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
2413
|
-
mcpServers: servers
|
|
2414
|
-
});
|
|
2415
|
-
if (isJsonRpcError(result)) {
|
|
2416
|
-
throw new ACPSessionError("prompt_failed", `session/resume failed: ${result.message}`, result);
|
|
2417
|
-
}
|
|
2418
|
-
ctx.setSessionId(sessionId);
|
|
2419
|
-
}
|
|
2420
|
-
async function executeListSessions(ctx, cursor, cwd) {
|
|
2421
|
-
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2422
|
-
if (!ctx.agentCapabilities.sessionCapabilities?.list) {
|
|
2423
|
-
throw new ACPSessionError(
|
|
2424
|
-
"unsupported_capability",
|
|
2425
|
-
"agent does not support session/list (sessionCapabilities.list not advertised)"
|
|
2426
|
-
);
|
|
2427
|
-
}
|
|
2428
|
-
const id = ctx.allocId();
|
|
2429
|
-
const params = {};
|
|
2430
|
-
if (cursor !== void 0) params.cursor = cursor;
|
|
2431
|
-
if (cwd !== void 0) params.cwd = cwd;
|
|
2432
|
-
const result = await ctx.sendRequest(id, "session/list", params);
|
|
2433
|
-
if (isJsonRpcError(result)) {
|
|
2434
|
-
throw new ACPSessionError("prompt_failed", `session/list failed: ${result.message}`, result);
|
|
2551
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2552
|
+
await sender.sendErrorResponse(id, -32603, message);
|
|
2435
2553
|
}
|
|
2436
|
-
const r = result;
|
|
2437
|
-
return {
|
|
2438
|
-
sessions: r.sessions ?? [],
|
|
2439
|
-
nextCursor: r.nextCursor
|
|
2440
|
-
};
|
|
2441
2554
|
}
|
|
2442
|
-
async function
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2555
|
+
async function authorizeAcpCallback(permissionPolicy, partial, callbackOptions) {
|
|
2556
|
+
try {
|
|
2557
|
+
const outcome = await runPermissionWithDeadline(
|
|
2558
|
+
permissionPolicy,
|
|
2559
|
+
{
|
|
2560
|
+
toolCall: {
|
|
2561
|
+
sessionUpdate: "tool_call_update",
|
|
2562
|
+
toolCallId: partial.toolCallId,
|
|
2563
|
+
title: partial.title,
|
|
2564
|
+
kind: partial.kind,
|
|
2565
|
+
status: "pending",
|
|
2566
|
+
rawInput: partial.rawInput
|
|
2567
|
+
},
|
|
2568
|
+
options: [
|
|
2569
|
+
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
2570
|
+
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
2571
|
+
]
|
|
2572
|
+
},
|
|
2573
|
+
callbackOptions
|
|
2448
2574
|
);
|
|
2575
|
+
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always" ? "allowed" : "denied";
|
|
2576
|
+
} catch (err) {
|
|
2577
|
+
return isAbortLikeError(err) ? "cancelled" : "denied";
|
|
2449
2578
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2579
|
+
}
|
|
2580
|
+
async function runPermissionWithDeadline(permissionPolicy, call, callbackOptions) {
|
|
2581
|
+
const timeoutMs = resolvePermissionDeadline(callbackOptions.permissionTimeoutMs);
|
|
2582
|
+
const controller = new AbortController();
|
|
2583
|
+
const abort = () => controller.abort();
|
|
2584
|
+
const timer = timeoutMs === null ? null : setTimeout(abort, timeoutMs);
|
|
2585
|
+
let removeAbort;
|
|
2586
|
+
if (callbackOptions.signal) {
|
|
2587
|
+
if (callbackOptions.signal.aborted) {
|
|
2588
|
+
abort();
|
|
2589
|
+
} else {
|
|
2590
|
+
callbackOptions.signal.addEventListener("abort", abort, { once: true });
|
|
2591
|
+
removeAbort = () => callbackOptions.signal?.removeEventListener("abort", abort);
|
|
2592
|
+
}
|
|
2454
2593
|
}
|
|
2455
|
-
|
|
2456
|
-
|
|
2594
|
+
try {
|
|
2595
|
+
return await Promise.race([
|
|
2596
|
+
permissionPolicy({
|
|
2597
|
+
toolCall: call.toolCall,
|
|
2598
|
+
options: call.options,
|
|
2599
|
+
signal: controller.signal
|
|
2600
|
+
}),
|
|
2601
|
+
rejectOnAbort(controller.signal)
|
|
2602
|
+
]);
|
|
2603
|
+
} finally {
|
|
2604
|
+
if (timer !== null) clearTimeout(timer);
|
|
2605
|
+
removeAbort?.();
|
|
2457
2606
|
}
|
|
2458
2607
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2608
|
+
function rejectOnAbort(signal) {
|
|
2609
|
+
return new Promise((_, reject) => {
|
|
2610
|
+
const rejectAbort = () => reject(new Error("permission request cancelled or timed out"));
|
|
2611
|
+
if (signal.aborted) {
|
|
2612
|
+
rejectAbort();
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
2467
2616
|
});
|
|
2468
|
-
if (isJsonRpcError(result)) {
|
|
2469
|
-
throw new ACPSessionError("prompt_failed", `session/fork failed: ${result.message}`, result);
|
|
2470
|
-
}
|
|
2471
|
-
const newId = result.sessionId;
|
|
2472
|
-
if (typeof newId !== "string" || !newId) {
|
|
2473
|
-
throw new ACPSessionError("protocol_error", "session/fork returned no sessionId", result);
|
|
2474
|
-
}
|
|
2475
|
-
return newId;
|
|
2476
2617
|
}
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
const id = ctx.allocId();
|
|
2480
|
-
const result = await ctx.sendRequest(id, "session/set_mode", { sessionId, modeId });
|
|
2481
|
-
if (isJsonRpcError(result)) {
|
|
2482
|
-
throw new ACPSessionError(
|
|
2483
|
-
"prompt_failed",
|
|
2484
|
-
`session/set_mode failed: ${result.message}`,
|
|
2485
|
-
result
|
|
2486
|
-
);
|
|
2487
|
-
}
|
|
2618
|
+
function isAbortLikeError(err) {
|
|
2619
|
+
return err instanceof Error && /cancelled|canceled|timed out|aborted/i.test(err.message);
|
|
2488
2620
|
}
|
|
2489
|
-
|
|
2490
|
-
if (
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
sessionId,
|
|
2494
|
-
configId,
|
|
2495
|
-
value
|
|
2496
|
-
});
|
|
2497
|
-
if (isJsonRpcError(result)) {
|
|
2498
|
-
throw new ACPSessionError(
|
|
2499
|
-
"prompt_failed",
|
|
2500
|
-
`session/set_config_option failed: ${result.message}`,
|
|
2501
|
-
result
|
|
2502
|
-
);
|
|
2503
|
-
}
|
|
2621
|
+
function resolvePermissionDeadline(value) {
|
|
2622
|
+
if (value === Number.POSITIVE_INFINITY) return null;
|
|
2623
|
+
if (value !== void 0 && Number.isFinite(value) && value > 0) return Math.trunc(value);
|
|
2624
|
+
return DEFAULT_PERMISSION_TIMEOUT_MS;
|
|
2504
2625
|
}
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
if (isJsonRpcError(result)) {
|
|
2510
|
-
throw new ACPSessionError("prompt_failed", `providers/list failed: ${result.message}`, result);
|
|
2511
|
-
}
|
|
2512
|
-
const r = result;
|
|
2513
|
-
return { providers: r.providers ?? [], currentProviderId: r.currentProviderId ?? null };
|
|
2626
|
+
|
|
2627
|
+
// src/client/acp-session-content.ts
|
|
2628
|
+
function textContent(text) {
|
|
2629
|
+
return { type: "text", text };
|
|
2514
2630
|
}
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
const id = ctx.allocId();
|
|
2518
|
-
const result = await ctx.sendRequest(id, "providers/set", { providerId, ...config ?? {} });
|
|
2519
|
-
if (isJsonRpcError(result)) {
|
|
2520
|
-
throw new ACPSessionError("prompt_failed", `providers/set failed: ${result.message}`, result);
|
|
2521
|
-
}
|
|
2631
|
+
function imageContent(mimeType, data) {
|
|
2632
|
+
return { type: "image", mimeType, data };
|
|
2522
2633
|
}
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
const id = ctx.allocId();
|
|
2526
|
-
const result = await ctx.sendRequest(id, "providers/disable", {});
|
|
2527
|
-
if (isJsonRpcError(result)) {
|
|
2528
|
-
throw new ACPSessionError(
|
|
2529
|
-
"prompt_failed",
|
|
2530
|
-
`providers/disable failed: ${result.message}`,
|
|
2531
|
-
result
|
|
2532
|
-
);
|
|
2533
|
-
}
|
|
2634
|
+
function audioContent(mimeType, data) {
|
|
2635
|
+
return { type: "audio", mimeType, data };
|
|
2534
2636
|
}
|
|
2535
|
-
|
|
2536
|
-
if (
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2539
|
-
if (
|
|
2540
|
-
|
|
2637
|
+
function extractText(block) {
|
|
2638
|
+
if (typeof block !== "object" || block === null) return "";
|
|
2639
|
+
const b = block;
|
|
2640
|
+
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
2641
|
+
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
2642
|
+
return b.resource.text;
|
|
2541
2643
|
}
|
|
2542
|
-
return
|
|
2644
|
+
return "";
|
|
2543
2645
|
}
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
);
|
|
2557
|
-
}
|
|
2558
|
-
const sessionId = result.sessionId;
|
|
2559
|
-
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
2560
|
-
throw new ACPSessionError("protocol_error", "session/new returned no sessionId", result);
|
|
2561
|
-
}
|
|
2562
|
-
return sessionId;
|
|
2646
|
+
function isRecord(v) {
|
|
2647
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2648
|
+
}
|
|
2649
|
+
function emptyRunResult(stopReason) {
|
|
2650
|
+
return {
|
|
2651
|
+
text: "",
|
|
2652
|
+
stopReason,
|
|
2653
|
+
hasText: false,
|
|
2654
|
+
toolCalls: [],
|
|
2655
|
+
diffs: [],
|
|
2656
|
+
thoughts: ""
|
|
2657
|
+
};
|
|
2563
2658
|
}
|
|
2564
2659
|
|
|
2565
2660
|
// src/client/acp-session-updates.ts
|
|
@@ -2699,11 +2794,30 @@ function makePermissionPolicy(decide) {
|
|
|
2699
2794
|
// src/client/terminal-server.ts
|
|
2700
2795
|
import { spawn } from "node:child_process";
|
|
2701
2796
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2702
|
-
import { realpathSync as realpathSync2 } from "node:fs";
|
|
2797
|
+
import { accessSync, realpathSync as realpathSync2 } from "node:fs";
|
|
2703
2798
|
import * as path3 from "node:path";
|
|
2704
2799
|
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
2705
2800
|
import { treeKill as treeKill2 } from "@wrongstack/core/utils/tree-kill";
|
|
2706
2801
|
var EMPTY_BUFFER = Buffer.alloc(0);
|
|
2802
|
+
function win32BatchTarget(command) {
|
|
2803
|
+
if (/\.(?:cmd|bat)$/i.test(command)) return command;
|
|
2804
|
+
if (/[\\/]/.test(command) || path3.extname(command)) return void 0;
|
|
2805
|
+
const exts = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD").toLowerCase().split(";");
|
|
2806
|
+
for (const dir of (process.env["PATH"] ?? "").split(path3.delimiter)) {
|
|
2807
|
+
if (!dir) continue;
|
|
2808
|
+
for (const ext of exts) {
|
|
2809
|
+
if (!ext) continue;
|
|
2810
|
+
const full = path3.join(dir, `${command}${ext}`);
|
|
2811
|
+
try {
|
|
2812
|
+
accessSync(full);
|
|
2813
|
+
} catch {
|
|
2814
|
+
continue;
|
|
2815
|
+
}
|
|
2816
|
+
return ext === ".cmd" || ext === ".bat" ? full : void 0;
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
return void 0;
|
|
2820
|
+
}
|
|
2707
2821
|
var DEBUG_DISPOSE = typeof process !== "undefined" && !!process.env?.WRONGSTACK_DEBUG && process.env.WRONGSTACK_DEBUG !== "0" && process.env.WRONGSTACK_DEBUG !== "false";
|
|
2708
2822
|
var TerminalServer = class {
|
|
2709
2823
|
terminals = /* @__PURE__ */ new Map();
|
|
@@ -2754,11 +2868,14 @@ var TerminalServer = class {
|
|
|
2754
2868
|
Math.max(1, this.clampFiniteInt(params.outputByteLimit, this.outputByteLimit)),
|
|
2755
2869
|
this.maxOutputByteLimit
|
|
2756
2870
|
);
|
|
2757
|
-
const
|
|
2871
|
+
const batch = process.platform === "win32" ? win32BatchTarget(params.command) : void 0;
|
|
2872
|
+
const shim = batch ? buildWin32CmdShimInvocation(batch, params.args ?? []) : void 0;
|
|
2873
|
+
const proc = spawn(shim?.command ?? params.command, shim?.args ?? params.args ?? [], {
|
|
2758
2874
|
cwd,
|
|
2759
2875
|
env: this.buildEnv(params.env),
|
|
2760
2876
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2761
|
-
windowsHide: true
|
|
2877
|
+
windowsHide: true,
|
|
2878
|
+
...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
|
|
2762
2879
|
// shell: false on purpose. The terminal server is invoked with
|
|
2763
2880
|
// the agent's explicit argv; turning on shell-mode would make
|
|
2764
2881
|
// the command a single shell-parsed string, which breaks
|
|
@@ -3435,55 +3552,14 @@ var ACPSession = class _ACPSession {
|
|
|
3435
3552
|
* Call this AFTER start() and BEFORE any session/new call.
|
|
3436
3553
|
*/
|
|
3437
3554
|
async authenticate(methodId) {
|
|
3438
|
-
|
|
3439
|
-
throw new ACPSessionError("closed", "session is closed");
|
|
3440
|
-
}
|
|
3441
|
-
if (this.state !== "ready" && this.state !== "authenticated") {
|
|
3442
|
-
throw new ACPSessionError(
|
|
3443
|
-
"protocol_error",
|
|
3444
|
-
`authenticate called in state=${this.state} (expected 'ready')`
|
|
3445
|
-
);
|
|
3446
|
-
}
|
|
3447
|
-
if (this.state === "authenticated") return;
|
|
3448
|
-
if (!this.authMethods.some((m) => m.id === methodId)) {
|
|
3449
|
-
throw new ACPSessionError(
|
|
3450
|
-
"auth_failed",
|
|
3451
|
-
`auth method "${methodId}" not in advertised methods: ${this.authMethods.map((m) => m.id).join(", ")}`
|
|
3452
|
-
);
|
|
3453
|
-
}
|
|
3454
|
-
if (this.authMethods.find((m) => m.id === methodId)?.type === "terminal") {
|
|
3455
|
-
throw new ACPSessionError(
|
|
3456
|
-
"auth_failed",
|
|
3457
|
-
"Terminal authentication requires interactive login followed by reconnect; it cannot use authenticate"
|
|
3458
|
-
);
|
|
3459
|
-
}
|
|
3460
|
-
const id = this.allocId();
|
|
3461
|
-
const result = await this.sendRequest(id, "authenticate", { methodId });
|
|
3462
|
-
if (isJsonRpcError(result)) {
|
|
3463
|
-
throw new ACPSessionError("auth_failed", `authenticate failed: ${result.message}`, result);
|
|
3464
|
-
}
|
|
3465
|
-
this.state = "authenticated";
|
|
3555
|
+
return authenticate(this.acpSessionAuthHost(), methodId);
|
|
3466
3556
|
}
|
|
3467
3557
|
/**
|
|
3468
3558
|
* Log out from the current authenticated session.
|
|
3469
3559
|
* Only callable if the agent advertises `auth.logout` capability.
|
|
3470
3560
|
*/
|
|
3471
3561
|
async logout() {
|
|
3472
|
-
|
|
3473
|
-
throw new ACPSessionError("closed", "session is closed");
|
|
3474
|
-
}
|
|
3475
|
-
if (!this.agentCapabilities.auth?.logout) {
|
|
3476
|
-
throw new ACPSessionError(
|
|
3477
|
-
"unsupported_capability",
|
|
3478
|
-
"agent does not support logout (auth.logout capability not advertised)"
|
|
3479
|
-
);
|
|
3480
|
-
}
|
|
3481
|
-
const id = this.allocId();
|
|
3482
|
-
const result = await this.sendRequest(id, "logout", {});
|
|
3483
|
-
if (isJsonRpcError(result)) {
|
|
3484
|
-
throw new ACPSessionError("logout_failed", `logout failed: ${result.message}`, result);
|
|
3485
|
-
}
|
|
3486
|
-
this.state = "ready";
|
|
3562
|
+
return logout(this.acpSessionAuthHost());
|
|
3487
3563
|
}
|
|
3488
3564
|
// ──────────────────────────────────────────────────────────────────────
|
|
3489
3565
|
// Session management delegation
|
|
@@ -3781,19 +3857,7 @@ var ACPSession = class _ACPSession {
|
|
|
3781
3857
|
* `authMethods`; we do not pop OAuth on every spawn.
|
|
3782
3858
|
*/
|
|
3783
3859
|
async createSessionWithAuth() {
|
|
3784
|
-
|
|
3785
|
-
return await executeCreateSession(this.opContext());
|
|
3786
|
-
} catch (err) {
|
|
3787
|
-
if (this.state === "authenticated" || !isAuthRequiredError(err)) {
|
|
3788
|
-
throw err instanceof ACPSessionError ? err : new ACPSessionError(
|
|
3789
|
-
"session_create_failed",
|
|
3790
|
-
err instanceof Error ? err.message : String(err),
|
|
3791
|
-
err
|
|
3792
|
-
);
|
|
3793
|
-
}
|
|
3794
|
-
await this.ensureAuthenticated();
|
|
3795
|
-
return executeCreateSession(this.opContext());
|
|
3796
|
-
}
|
|
3860
|
+
return createSessionWithAuth(this.acpSessionAuthHost());
|
|
3797
3861
|
}
|
|
3798
3862
|
/**
|
|
3799
3863
|
* Pick a non-terminal auth method and run `authenticate`. Terminal-only
|
|
@@ -3801,27 +3865,7 @@ var ACPSession = class _ACPSession {
|
|
|
3801
3865
|
* we refuse rather than hang a TUI inside the JSON-RPC child.
|
|
3802
3866
|
*/
|
|
3803
3867
|
async ensureAuthenticated() {
|
|
3804
|
-
|
|
3805
|
-
if (this.authMethods.length === 0) {
|
|
3806
|
-
throw new ACPSessionError(
|
|
3807
|
-
"auth_failed",
|
|
3808
|
-
"This agent requires authentication before a session can start, but advertised no authMethods. Log into the CLI, then retry."
|
|
3809
|
-
);
|
|
3810
|
-
}
|
|
3811
|
-
const inProcess = this.authMethods.find(
|
|
3812
|
-
(m) => m.type === void 0 || m.type === "agent" || m.type === "oauth" || m.type === "http"
|
|
3813
|
-
);
|
|
3814
|
-
if (inProcess) {
|
|
3815
|
-
await this.authenticate(inProcess.id);
|
|
3816
|
-
return;
|
|
3817
|
-
}
|
|
3818
|
-
const terminal = this.authMethods.find((m) => m.type === "terminal");
|
|
3819
|
-
const setupArgs = terminal?.args?.length ? terminal.args.join(" ") : void 0;
|
|
3820
|
-
const setup = setupArgs !== void 0 ? `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""} ${setupArgs}` : `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""}`;
|
|
3821
|
-
throw new ACPSessionError(
|
|
3822
|
-
"auth_failed",
|
|
3823
|
-
`This agent requires a terminal login before ACP can start. Run \`${setup}\` (or the CLI's /login), then retry.`
|
|
3824
|
-
);
|
|
3868
|
+
return ensureAuthenticated(this.acpSessionAuthHost());
|
|
3825
3869
|
}
|
|
3826
3870
|
sendResult(id, result) {
|
|
3827
3871
|
return this.transport.send({ jsonrpc: "2.0", id, result });
|
|
@@ -3937,6 +3981,29 @@ var ACPSession = class _ACPSession {
|
|
|
3937
3981
|
resetScratch() {
|
|
3938
3982
|
this.scratch = createSessionScratch();
|
|
3939
3983
|
}
|
|
3984
|
+
acpSessionAuthHost() {
|
|
3985
|
+
const self = this;
|
|
3986
|
+
return {
|
|
3987
|
+
get state() {
|
|
3988
|
+
return self.state;
|
|
3989
|
+
},
|
|
3990
|
+
set state(value) {
|
|
3991
|
+
self.state = value;
|
|
3992
|
+
},
|
|
3993
|
+
get authMethods() {
|
|
3994
|
+
return self.authMethods;
|
|
3995
|
+
},
|
|
3996
|
+
allocId: (...args) => this.allocId(...args),
|
|
3997
|
+
sendRequest: (...args) => this.sendRequest(...args),
|
|
3998
|
+
get agentCapabilities() {
|
|
3999
|
+
return self.agentCapabilities;
|
|
4000
|
+
},
|
|
4001
|
+
opContext: (...args) => this.opContext(...args),
|
|
4002
|
+
ensureAuthenticated: (...args) => this.ensureAuthenticated(...args),
|
|
4003
|
+
authenticate: (...args) => this.authenticate(...args),
|
|
4004
|
+
opts: this.opts
|
|
4005
|
+
};
|
|
4006
|
+
}
|
|
3940
4007
|
};
|
|
3941
4008
|
|
|
3942
4009
|
// src/client/tool-translator.ts
|