@parall/daemon 1.29.3 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/manifest.json +8 -8
- package/bundle/parall-claude-agent.js +122 -72
- package/bundle/parall-codex-agent.js +128 -83
- package/bundle/parall-daemon.js +259 -104
- package/dist/filesystem.d.ts +7 -0
- package/dist/filesystem.d.ts.map +1 -0
- package/dist/filesystem.js +118 -0
- package/dist/supervisor.d.ts +1 -0
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +30 -0
- package/package.json +6 -6
package/bundle/parall-daemon.js
CHANGED
|
@@ -5,7 +5,8 @@ function createLogger(prefix) {
|
|
|
5
5
|
return {
|
|
6
6
|
info: (msg) => console.log(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`),
|
|
7
7
|
warn: (msg) => console.warn(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`),
|
|
8
|
-
error: (msg) => console.error(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`)
|
|
8
|
+
error: (msg) => console.error(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`),
|
|
9
|
+
child: (sub) => createLogger(`${prefix}:${sub}`)
|
|
9
10
|
};
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -82,6 +83,7 @@ var ENDPOINTS = {
|
|
|
82
83
|
AGENT_ACTIVITY: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/activity`,
|
|
83
84
|
AGENT_MONITOR: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/monitor`,
|
|
84
85
|
AGENT_ME: (orgId) => `${API_BASE}/orgs/${orgId}/agents/me`,
|
|
86
|
+
AGENT_NEW_SESSION: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/new-session`,
|
|
85
87
|
AGENT_SESSIONS: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions`,
|
|
86
88
|
AGENT_SESSION: (orgId, agentId, sessionId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}`,
|
|
87
89
|
AGENT_SESSION_STEPS: (orgId, agentId, sessionId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}/steps`,
|
|
@@ -120,6 +122,7 @@ var ENDPOINTS = {
|
|
|
120
122
|
MACHINE_KEY: (orgId, machineId, keyId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/keys/${keyId}`,
|
|
121
123
|
MACHINE_RUNTIME_AUTH_SESSIONS: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth-sessions`,
|
|
122
124
|
MACHINE_RUNTIME_AUTH_SESSION_COMPLETE: (orgId, machineId, sessionId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth-sessions/${sessionId}/complete`,
|
|
125
|
+
MACHINE_BROWSE: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/browse`,
|
|
123
126
|
// Machine self-control-plane (mck_-scoped). The bearer token implicitly
|
|
124
127
|
// identifies the Machine, so there is no `:mid` URL parameter — these are
|
|
125
128
|
// "self" routes called by the daemon for its own host.
|
|
@@ -129,6 +132,7 @@ var ENDPOINTS = {
|
|
|
129
132
|
MACHINES_ME_AGENT_LAUNCH_CREDENTIAL: (agentId) => `${API_BASE}/machines/me/agents/${agentId}/launch-credential`,
|
|
130
133
|
MACHINES_ME_AGENT_WORKSPACE_STATE: (agentId) => `${API_BASE}/machines/me/agents/${agentId}/workspace-state`,
|
|
131
134
|
MACHINES_ME_WS_TICKET: `${API_BASE}/machines/me/ws/ticket`,
|
|
135
|
+
MACHINES_ME_BROWSE_RESPONSE: (requestId) => `${API_BASE}/machines/me/browse-response/${requestId}`,
|
|
132
136
|
// Tasks (org-scoped)
|
|
133
137
|
TASKS: (orgId) => `${API_BASE}/orgs/${orgId}/tasks`,
|
|
134
138
|
TASK: (orgId, taskId) => `${API_BASE}/orgs/${orgId}/tasks/${taskId}`,
|
|
@@ -308,7 +312,9 @@ var WS_EVENTS = {
|
|
|
308
312
|
MACHINE_AGENT_ATTACHED: "machine.agent.attached",
|
|
309
313
|
MACHINE_AGENT_DETACHED: "machine.agent.detached",
|
|
310
314
|
MACHINE_STOP: "machine.stop",
|
|
311
|
-
MACHINE_WORKSPACE_SETUP_REQUESTED: "machine.workspace.setup.requested"
|
|
315
|
+
MACHINE_WORKSPACE_SETUP_REQUESTED: "machine.workspace.setup.requested",
|
|
316
|
+
MACHINE_FILESYSTEM_BROWSE: "machine.filesystem.browse",
|
|
317
|
+
AGENT_NEW_SESSION: "agent.new_session"
|
|
312
318
|
};
|
|
313
319
|
|
|
314
320
|
// ts/sdk/dist/client.js
|
|
@@ -391,10 +397,10 @@ var ParallClient = class _ParallClient {
|
|
|
391
397
|
* REFRESH_THRESHOLD_S, refresh it **before** sending the request.
|
|
392
398
|
* No-op when the token is still fresh, missing, or un-parseable.
|
|
393
399
|
*/
|
|
394
|
-
async ensureFreshToken(
|
|
400
|
+
async ensureFreshToken(path7) {
|
|
395
401
|
if (!this.token || !this.getRefreshToken)
|
|
396
402
|
return;
|
|
397
|
-
const pathSuffix =
|
|
403
|
+
const pathSuffix = path7.replace(/^\/api\/v1/, "");
|
|
398
404
|
if (_ParallClient.AUTH_PATHS.has(pathSuffix))
|
|
399
405
|
return;
|
|
400
406
|
const exp = _ParallClient.decodeJwtExp(this.token);
|
|
@@ -426,11 +432,11 @@ var ParallClient = class _ParallClient {
|
|
|
426
432
|
this.refreshPromise = null;
|
|
427
433
|
}
|
|
428
434
|
}
|
|
429
|
-
async request(method,
|
|
435
|
+
async request(method, path7, body, query, retried = false, opts) {
|
|
430
436
|
if (!retried) {
|
|
431
|
-
await this.ensureFreshToken(
|
|
437
|
+
await this.ensureFreshToken(path7);
|
|
432
438
|
}
|
|
433
|
-
let url = `${this.baseUrl}${
|
|
439
|
+
let url = `${this.baseUrl}${path7}`;
|
|
434
440
|
if (query) {
|
|
435
441
|
const params = new URLSearchParams();
|
|
436
442
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -455,12 +461,12 @@ var ParallClient = class _ParallClient {
|
|
|
455
461
|
throw _ParallClient.normalizeFetchError(err);
|
|
456
462
|
}
|
|
457
463
|
if (res.status === 401) {
|
|
458
|
-
const pathSuffix =
|
|
464
|
+
const pathSuffix = path7.replace(/^\/api\/v1/, "");
|
|
459
465
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
460
466
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
461
467
|
const refreshed = await this.tryRefresh();
|
|
462
468
|
if (refreshed) {
|
|
463
|
-
return this.request(method,
|
|
469
|
+
return this.request(method, path7, body, query, true, opts);
|
|
464
470
|
}
|
|
465
471
|
}
|
|
466
472
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -497,15 +503,15 @@ var ParallClient = class _ParallClient {
|
|
|
497
503
|
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
498
504
|
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
499
505
|
*/
|
|
500
|
-
async multipartRequest(method,
|
|
506
|
+
async multipartRequest(method, path7, body, retried = false) {
|
|
501
507
|
if (!retried) {
|
|
502
|
-
await this.ensureFreshToken(
|
|
508
|
+
await this.ensureFreshToken(path7);
|
|
503
509
|
}
|
|
504
510
|
const { "Content-Type": _drop, ...headers } = this.buildHeaders();
|
|
505
511
|
void _drop;
|
|
506
512
|
let res;
|
|
507
513
|
try {
|
|
508
|
-
res = await fetch(`${this.baseUrl}${
|
|
514
|
+
res = await fetch(`${this.baseUrl}${path7}`, {
|
|
509
515
|
method,
|
|
510
516
|
headers,
|
|
511
517
|
body,
|
|
@@ -515,12 +521,12 @@ var ParallClient = class _ParallClient {
|
|
|
515
521
|
throw _ParallClient.normalizeFetchError(err);
|
|
516
522
|
}
|
|
517
523
|
if (res.status === 401) {
|
|
518
|
-
const pathSuffix =
|
|
524
|
+
const pathSuffix = path7.replace(/^\/api\/v1/, "");
|
|
519
525
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
520
526
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
521
527
|
const refreshed = await this.tryRefresh();
|
|
522
528
|
if (refreshed) {
|
|
523
|
-
return this.multipartRequest(method,
|
|
529
|
+
return this.multipartRequest(method, path7, body, true);
|
|
524
530
|
}
|
|
525
531
|
}
|
|
526
532
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -854,6 +860,9 @@ var ParallClient = class _ParallClient {
|
|
|
854
860
|
return this.request("GET", ENDPOINTS.AGENT_ME(orgId));
|
|
855
861
|
}
|
|
856
862
|
// ---- Agent Sessions (org-scoped) ----
|
|
863
|
+
async requestNewAgentSession(orgId, agentId) {
|
|
864
|
+
return this.request("POST", ENDPOINTS.AGENT_NEW_SESSION(orgId, agentId));
|
|
865
|
+
}
|
|
857
866
|
async createAgentSession(orgId, agentId, req) {
|
|
858
867
|
return this.request("POST", ENDPOINTS.AGENT_SESSIONS(orgId, agentId), req);
|
|
859
868
|
}
|
|
@@ -1053,9 +1062,15 @@ var ParallClient = class _ParallClient {
|
|
|
1053
1062
|
async getMachineWsTicket() {
|
|
1054
1063
|
return this.request("POST", ENDPOINTS.MACHINES_ME_WS_TICKET);
|
|
1055
1064
|
}
|
|
1065
|
+
async postBrowseResponse(requestId, response) {
|
|
1066
|
+
return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSE_RESPONSE(requestId), response);
|
|
1067
|
+
}
|
|
1056
1068
|
async resizeMachine(orgId, machineId, spec) {
|
|
1057
1069
|
return this.request("PATCH", ENDPOINTS.MACHINE_SPEC(orgId, machineId), spec);
|
|
1058
1070
|
}
|
|
1071
|
+
async browseMachineFilesystem(orgId, machineId, path7) {
|
|
1072
|
+
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path: path7 }, void 0, false, { timeoutMs: 15e3 });
|
|
1073
|
+
}
|
|
1059
1074
|
/** Create a new machine key. Returns the raw key string (shown once) + metadata. */
|
|
1060
1075
|
async createMachineKey(orgId, machineId, name) {
|
|
1061
1076
|
return this.request("POST", ENDPOINTS.MACHINE_KEYS(orgId, machineId), name ? { name } : void 0);
|
|
@@ -1404,8 +1419,8 @@ var ParallClient = class _ParallClient {
|
|
|
1404
1419
|
async deleteWikiPathScope(orgId, wikiId, scopeId) {
|
|
1405
1420
|
await this.request("DELETE", ENDPOINTS.WIKI_PATH_SCOPE(orgId, wikiId, scopeId));
|
|
1406
1421
|
}
|
|
1407
|
-
async getWikiAccessStatus(orgId, wikiId,
|
|
1408
|
-
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0,
|
|
1422
|
+
async getWikiAccessStatus(orgId, wikiId, path7) {
|
|
1423
|
+
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path7 ? { path: path7 } : void 0);
|
|
1409
1424
|
}
|
|
1410
1425
|
async createWikiAccessRequest(orgId, wikiId, data) {
|
|
1411
1426
|
await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
|
|
@@ -1414,11 +1429,11 @@ var ParallClient = class _ParallClient {
|
|
|
1414
1429
|
async getWikiCommits(orgId, wikiId, params) {
|
|
1415
1430
|
return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
|
|
1416
1431
|
}
|
|
1417
|
-
async getWikiFileCommits(orgId, wikiId,
|
|
1418
|
-
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, { path:
|
|
1432
|
+
async getWikiFileCommits(orgId, wikiId, path7, params) {
|
|
1433
|
+
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, { path: path7, ...params });
|
|
1419
1434
|
}
|
|
1420
|
-
async getWikiBlame(orgId, wikiId,
|
|
1421
|
-
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path:
|
|
1435
|
+
async getWikiBlame(orgId, wikiId, path7, ref) {
|
|
1436
|
+
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path7, ref });
|
|
1422
1437
|
}
|
|
1423
1438
|
// ---- Wiki Operations (audit log) ----
|
|
1424
1439
|
async getWikiOperations(orgId, wikiId, params) {
|
|
@@ -2016,11 +2031,125 @@ function resolveWsUrl(apiUrl, explicitWsUrl, swimlaneName) {
|
|
|
2016
2031
|
|
|
2017
2032
|
// ts/daemon/dist/supervisor.js
|
|
2018
2033
|
import { spawn as spawn2 } from "node:child_process";
|
|
2019
|
-
import * as
|
|
2020
|
-
import * as
|
|
2034
|
+
import * as fs4 from "node:fs";
|
|
2035
|
+
import * as path5 from "node:path";
|
|
2036
|
+
|
|
2037
|
+
// ts/daemon/dist/filesystem.js
|
|
2038
|
+
import * as fs2 from "fs";
|
|
2039
|
+
import * as path2 from "path";
|
|
2040
|
+
import * as os2 from "os";
|
|
2041
|
+
var MAX_ENTRIES = 200;
|
|
2042
|
+
var SYSTEM_DIR_PREFIXES = [
|
|
2043
|
+
"/Applications",
|
|
2044
|
+
"/bin",
|
|
2045
|
+
"/boot",
|
|
2046
|
+
"/dev",
|
|
2047
|
+
"/etc",
|
|
2048
|
+
"/Library",
|
|
2049
|
+
"/private",
|
|
2050
|
+
"/proc",
|
|
2051
|
+
"/root",
|
|
2052
|
+
"/run",
|
|
2053
|
+
"/sbin",
|
|
2054
|
+
"/System",
|
|
2055
|
+
"/sys",
|
|
2056
|
+
"/usr",
|
|
2057
|
+
"/var"
|
|
2058
|
+
];
|
|
2059
|
+
var CREDENTIAL_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
2060
|
+
".aws",
|
|
2061
|
+
".azure",
|
|
2062
|
+
".claude",
|
|
2063
|
+
".codex",
|
|
2064
|
+
".config",
|
|
2065
|
+
".docker",
|
|
2066
|
+
".gnupg",
|
|
2067
|
+
".kube",
|
|
2068
|
+
".npm",
|
|
2069
|
+
".ssh",
|
|
2070
|
+
".parall-agent",
|
|
2071
|
+
".parall-daemon"
|
|
2072
|
+
]);
|
|
2073
|
+
function browseDenyReason(value) {
|
|
2074
|
+
const normalized = path2.resolve(value).split(path2.sep).join("/");
|
|
2075
|
+
if (normalized === "/")
|
|
2076
|
+
return "";
|
|
2077
|
+
for (const prefix of SYSTEM_DIR_PREFIXES) {
|
|
2078
|
+
if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
|
|
2079
|
+
return "a system directory";
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
2083
|
+
for (const part of parts) {
|
|
2084
|
+
if (CREDENTIAL_DIR_NAMES.has(part)) {
|
|
2085
|
+
return "a credential or application state directory";
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return "";
|
|
2089
|
+
}
|
|
2090
|
+
function syntheticRoots() {
|
|
2091
|
+
const roots = [];
|
|
2092
|
+
const platform2 = os2.platform();
|
|
2093
|
+
const candidates = platform2 === "darwin" ? ["/Users", os2.homedir()] : ["/home", os2.homedir()];
|
|
2094
|
+
for (const dir of [...new Set(candidates)]) {
|
|
2095
|
+
try {
|
|
2096
|
+
fs2.accessSync(dir, fs2.constants.R_OK);
|
|
2097
|
+
roots.push({ name: dir, type: "dir" });
|
|
2098
|
+
} catch {
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
return roots;
|
|
2102
|
+
}
|
|
2103
|
+
async function listDirectory(dirPath) {
|
|
2104
|
+
const resolved = path2.resolve(dirPath);
|
|
2105
|
+
const normalized = resolved.split(path2.sep).join("/");
|
|
2106
|
+
if (normalized === "/") {
|
|
2107
|
+
return { entries: syntheticRoots() };
|
|
2108
|
+
}
|
|
2109
|
+
const deny = browseDenyReason(normalized);
|
|
2110
|
+
if (deny) {
|
|
2111
|
+
return { entries: [], error: `Access denied: ${deny}` };
|
|
2112
|
+
}
|
|
2113
|
+
let realPath;
|
|
2114
|
+
try {
|
|
2115
|
+
realPath = fs2.realpathSync(resolved);
|
|
2116
|
+
} catch (err) {
|
|
2117
|
+
const code = err.code;
|
|
2118
|
+
if (code === "ENOENT")
|
|
2119
|
+
return { entries: [], error: "Directory not found" };
|
|
2120
|
+
return { entries: [], error: "Permission denied" };
|
|
2121
|
+
}
|
|
2122
|
+
const realDeny = browseDenyReason(realPath.split(path2.sep).join("/"));
|
|
2123
|
+
if (realDeny) {
|
|
2124
|
+
return { entries: [], error: `Access denied: ${realDeny}` };
|
|
2125
|
+
}
|
|
2126
|
+
let dirents;
|
|
2127
|
+
try {
|
|
2128
|
+
dirents = fs2.readdirSync(realPath, { withFileTypes: true });
|
|
2129
|
+
} catch (err) {
|
|
2130
|
+
const code = err.code;
|
|
2131
|
+
if (code === "ENOENT")
|
|
2132
|
+
return { entries: [], error: "Directory not found" };
|
|
2133
|
+
if (code === "EACCES" || code === "EPERM")
|
|
2134
|
+
return { entries: [], error: "Permission denied" };
|
|
2135
|
+
return { entries: [], error: `Failed to read directory: ${code ?? String(err)}` };
|
|
2136
|
+
}
|
|
2137
|
+
const entries = [];
|
|
2138
|
+
for (const d of dirents) {
|
|
2139
|
+
if (!d.isDirectory())
|
|
2140
|
+
continue;
|
|
2141
|
+
if (d.name.startsWith("."))
|
|
2142
|
+
continue;
|
|
2143
|
+
entries.push({ name: d.name, type: "dir" });
|
|
2144
|
+
if (entries.length >= MAX_ENTRIES)
|
|
2145
|
+
break;
|
|
2146
|
+
}
|
|
2147
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
2148
|
+
return { entries };
|
|
2149
|
+
}
|
|
2021
2150
|
|
|
2022
2151
|
// ts/daemon/dist/runtimes.js
|
|
2023
|
-
import * as
|
|
2152
|
+
import * as path3 from "node:path";
|
|
2024
2153
|
function llmSource(pc) {
|
|
2025
2154
|
if (pc?.llm_source)
|
|
2026
2155
|
return pc.llm_source;
|
|
@@ -2077,7 +2206,7 @@ var codexAdapter = {
|
|
|
2077
2206
|
env.PRLL_AGENT_ID = agentId;
|
|
2078
2207
|
env.PRLL_CODEX_STATE_DIR = dirs.stateDir;
|
|
2079
2208
|
env.PRLL_CODEX_WORKSPACE_DIR = dirs.workspaceDir;
|
|
2080
|
-
env.PRLL_CODEX_HOME =
|
|
2209
|
+
env.PRLL_CODEX_HOME = path3.join(dirs.stateDir, ".codex");
|
|
2081
2210
|
const source = llmSource(pc);
|
|
2082
2211
|
if (source === "parall") {
|
|
2083
2212
|
env.OPENAI_API_KEY = apiKey;
|
|
@@ -2139,8 +2268,8 @@ function assertAgentKey(apiKey) {
|
|
|
2139
2268
|
// ts/daemon/dist/workspace.js
|
|
2140
2269
|
import { spawn } from "node:child_process";
|
|
2141
2270
|
import { createHash } from "node:crypto";
|
|
2142
|
-
import * as
|
|
2143
|
-
import * as
|
|
2271
|
+
import * as fs3 from "node:fs";
|
|
2272
|
+
import * as path4 from "node:path";
|
|
2144
2273
|
var OUTPUT_TAIL_LIMIT = 32 * 1024;
|
|
2145
2274
|
var DEFAULT_SETUP_TIMEOUT_SEC = 600;
|
|
2146
2275
|
async function prepareWorkspace(opts) {
|
|
@@ -2257,7 +2386,7 @@ function resolveWorkspaceDir(workspace, defaultWorkspaceDir) {
|
|
|
2257
2386
|
async function ensureWorkspace(plan, log2) {
|
|
2258
2387
|
const ws = plan.workspace;
|
|
2259
2388
|
if (ws.mode === "default") {
|
|
2260
|
-
|
|
2389
|
+
fs3.mkdirSync(plan.workspaceDir, { recursive: true });
|
|
2261
2390
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2262
2391
|
return;
|
|
2263
2392
|
}
|
|
@@ -2265,7 +2394,7 @@ async function ensureWorkspace(plan, log2) {
|
|
|
2265
2394
|
assertSafeCustomWorkspacePath(plan);
|
|
2266
2395
|
let st;
|
|
2267
2396
|
try {
|
|
2268
|
-
st =
|
|
2397
|
+
st = fs3.statSync(plan.workspaceDir);
|
|
2269
2398
|
} catch (err) {
|
|
2270
2399
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
2271
2400
|
throw new Error(`workspace path does not exist: ${plan.workspaceDir}`);
|
|
@@ -2275,7 +2404,7 @@ async function ensureWorkspace(plan, log2) {
|
|
|
2275
2404
|
if (!st.isDirectory()) {
|
|
2276
2405
|
throw new Error(`workspace path is not a directory: ${plan.workspaceDir}`);
|
|
2277
2406
|
}
|
|
2278
|
-
assertSafeCustomWorkspacePath(plan,
|
|
2407
|
+
assertSafeCustomWorkspacePath(plan, fs3.realpathSync(plan.workspaceDir));
|
|
2279
2408
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2280
2409
|
return;
|
|
2281
2410
|
}
|
|
@@ -2286,17 +2415,17 @@ async function ensureWorkspace(plan, log2) {
|
|
|
2286
2415
|
if (plan.customWorkspaceField) {
|
|
2287
2416
|
assertSafeCustomWorkspacePath(plan);
|
|
2288
2417
|
}
|
|
2289
|
-
if (!
|
|
2290
|
-
|
|
2291
|
-
assertWritableWorkspaceDir(
|
|
2418
|
+
if (!fs3.existsSync(plan.workspaceDir)) {
|
|
2419
|
+
fs3.mkdirSync(path4.dirname(plan.workspaceDir), { recursive: true });
|
|
2420
|
+
assertWritableWorkspaceDir(path4.dirname(plan.workspaceDir));
|
|
2292
2421
|
await runCommand("git", ["clone", remote, plan.workspaceDir], process.cwd());
|
|
2293
2422
|
} else {
|
|
2294
|
-
const st =
|
|
2423
|
+
const st = fs3.statSync(plan.workspaceDir);
|
|
2295
2424
|
if (!st.isDirectory()) {
|
|
2296
2425
|
throw new Error(`workspace path is not a directory: ${plan.workspaceDir}`);
|
|
2297
2426
|
}
|
|
2298
2427
|
if (plan.customWorkspaceField) {
|
|
2299
|
-
assertSafeCustomWorkspacePath(plan,
|
|
2428
|
+
assertSafeCustomWorkspacePath(plan, fs3.realpathSync(plan.workspaceDir));
|
|
2300
2429
|
}
|
|
2301
2430
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2302
2431
|
await ensureGitWorktree(plan.workspaceDir);
|
|
@@ -2320,13 +2449,13 @@ async function verifyExistingWorkspace(plan, log2) {
|
|
|
2320
2449
|
if (plan.customWorkspaceField) {
|
|
2321
2450
|
assertSafeCustomWorkspacePath(plan);
|
|
2322
2451
|
}
|
|
2323
|
-
const st =
|
|
2452
|
+
const st = fs3.statSync(plan.workspaceDir);
|
|
2324
2453
|
if (!st.isDirectory()) {
|
|
2325
2454
|
log2.warn(`workspace ready state ignored: path is not a directory: ${plan.workspaceDir}`);
|
|
2326
2455
|
return false;
|
|
2327
2456
|
}
|
|
2328
2457
|
if (plan.customWorkspaceField) {
|
|
2329
|
-
assertSafeCustomWorkspacePath(plan,
|
|
2458
|
+
assertSafeCustomWorkspacePath(plan, fs3.realpathSync(plan.workspaceDir));
|
|
2330
2459
|
}
|
|
2331
2460
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2332
2461
|
if (plan.workspace.mode === "git") {
|
|
@@ -2419,7 +2548,7 @@ async function tryGitOutput(cmd, args, cwd) {
|
|
|
2419
2548
|
}
|
|
2420
2549
|
}
|
|
2421
2550
|
function runCommand(cmd, args, cwd, timeoutMs = 12e4, env = process.env) {
|
|
2422
|
-
return new Promise((
|
|
2551
|
+
return new Promise((resolve5, reject) => {
|
|
2423
2552
|
let tail = "";
|
|
2424
2553
|
let timedOut = false;
|
|
2425
2554
|
let settled = false;
|
|
@@ -2461,7 +2590,7 @@ ${tail}`)));
|
|
|
2461
2590
|
return;
|
|
2462
2591
|
}
|
|
2463
2592
|
if (code === 0) {
|
|
2464
|
-
settle(() =>
|
|
2593
|
+
settle(() => resolve5(tail));
|
|
2465
2594
|
} else {
|
|
2466
2595
|
settle(() => reject(new Error(`command failed (${code ?? signal}): ${cmd} ${args.join(" ")}
|
|
2467
2596
|
${tail}`)));
|
|
@@ -2470,10 +2599,10 @@ ${tail}`)));
|
|
|
2470
2599
|
});
|
|
2471
2600
|
}
|
|
2472
2601
|
function requireAbsolute(value, field) {
|
|
2473
|
-
if (!value || !
|
|
2602
|
+
if (!value || !path4.isAbsolute(value)) {
|
|
2474
2603
|
throw new Error(`${field} must be an absolute path`);
|
|
2475
2604
|
}
|
|
2476
|
-
return
|
|
2605
|
+
return path4.resolve(value);
|
|
2477
2606
|
}
|
|
2478
2607
|
function assertSafeCustomWorkspacePath(plan, candidate = plan.workspaceDir) {
|
|
2479
2608
|
if (!plan.customWorkspaceField)
|
|
@@ -2483,17 +2612,17 @@ function assertSafeCustomWorkspacePath(plan, candidate = plan.workspaceDir) {
|
|
|
2483
2612
|
if (reason) {
|
|
2484
2613
|
throw new Error(`${plan.customWorkspaceField} must point to a project folder, not ${reason}: ${normalized}`);
|
|
2485
2614
|
}
|
|
2486
|
-
const defaultWorkspace =
|
|
2615
|
+
const defaultWorkspace = path4.resolve(plan.defaultWorkspaceDir);
|
|
2487
2616
|
if (isAncestorPath(normalized, defaultWorkspace) || normalized === defaultWorkspace) {
|
|
2488
2617
|
throw new Error(`${plan.customWorkspaceField} must not point at daemon state directories: ${normalized}`);
|
|
2489
2618
|
}
|
|
2490
2619
|
}
|
|
2491
2620
|
function assertWritableWorkspaceDir(dir) {
|
|
2492
|
-
|
|
2493
|
-
const probe =
|
|
2494
|
-
const fd =
|
|
2495
|
-
|
|
2496
|
-
|
|
2621
|
+
fs3.accessSync(dir, fs3.constants.R_OK | fs3.constants.W_OK | fs3.constants.X_OK);
|
|
2622
|
+
const probe = path4.join(dir, `.parall-workspace-check-${process.pid}-${Date.now()}`);
|
|
2623
|
+
const fd = fs3.openSync(probe, "wx", 384);
|
|
2624
|
+
fs3.closeSync(fd);
|
|
2625
|
+
fs3.unlinkSync(probe);
|
|
2497
2626
|
}
|
|
2498
2627
|
function workspacePathDenyReason(value) {
|
|
2499
2628
|
if (value === "/")
|
|
@@ -2554,11 +2683,11 @@ function workspacePathDenyReason(value) {
|
|
|
2554
2683
|
return "";
|
|
2555
2684
|
}
|
|
2556
2685
|
function isAncestorPath(parent, child) {
|
|
2557
|
-
const relative2 =
|
|
2558
|
-
return relative2 !== "" && !relative2.startsWith("..") && !
|
|
2686
|
+
const relative2 = path4.relative(parent, child);
|
|
2687
|
+
return relative2 !== "" && !relative2.startsWith("..") && !path4.isAbsolute(relative2);
|
|
2559
2688
|
}
|
|
2560
2689
|
function toPolicyPath(value) {
|
|
2561
|
-
return
|
|
2690
|
+
return path4.resolve(value).split(path4.sep).join("/");
|
|
2562
2691
|
}
|
|
2563
2692
|
function isNodeError(err) {
|
|
2564
2693
|
return err instanceof Error && "code" in err;
|
|
@@ -2574,14 +2703,14 @@ var WORKSPACE_SETUP_RETRY_DELAY_MS = 5e3;
|
|
|
2574
2703
|
function sleepCancellable(ms, signal) {
|
|
2575
2704
|
if (signal.aborted)
|
|
2576
2705
|
return Promise.resolve(false);
|
|
2577
|
-
return new Promise((
|
|
2706
|
+
return new Promise((resolve5) => {
|
|
2578
2707
|
const timer = setTimeout(() => {
|
|
2579
2708
|
signal.removeEventListener("abort", onAbort);
|
|
2580
|
-
|
|
2709
|
+
resolve5(true);
|
|
2581
2710
|
}, ms);
|
|
2582
2711
|
const onAbort = () => {
|
|
2583
2712
|
clearTimeout(timer);
|
|
2584
|
-
|
|
2713
|
+
resolve5(false);
|
|
2585
2714
|
};
|
|
2586
2715
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2587
2716
|
});
|
|
@@ -2659,6 +2788,10 @@ var DaemonSupervisor = class {
|
|
|
2659
2788
|
this.log.info(`WS: workspace setup requested for agent ${data.agent_id}`);
|
|
2660
2789
|
void this.handleWorkspaceSetupRequested(data.agent_id);
|
|
2661
2790
|
});
|
|
2791
|
+
this.ws.on("machine.filesystem.browse", (data) => {
|
|
2792
|
+
this.log.info(`WS: filesystem browse requested: ${data.path}`);
|
|
2793
|
+
void this.handleFilesystemBrowse(data.request_id, data.path);
|
|
2794
|
+
});
|
|
2662
2795
|
this.ws.on("machine.stop", (data) => {
|
|
2663
2796
|
this.log.info(`WS: machine.stop received (reason=${data.reason ?? "none"})`);
|
|
2664
2797
|
void this.stop();
|
|
@@ -2669,8 +2802,8 @@ var DaemonSupervisor = class {
|
|
|
2669
2802
|
}
|
|
2670
2803
|
});
|
|
2671
2804
|
await this.ws.connect();
|
|
2672
|
-
await new Promise((
|
|
2673
|
-
this.stopResolve =
|
|
2805
|
+
await new Promise((resolve5) => {
|
|
2806
|
+
this.stopResolve = resolve5;
|
|
2674
2807
|
});
|
|
2675
2808
|
signal.removeEventListener("abort", onAbort);
|
|
2676
2809
|
}
|
|
@@ -2789,15 +2922,15 @@ var DaemonSupervisor = class {
|
|
|
2789
2922
|
*/
|
|
2790
2923
|
migrateFlatLayout() {
|
|
2791
2924
|
const root = this.config.rootStateDir;
|
|
2792
|
-
const agentsDir =
|
|
2793
|
-
const flatWorkspace =
|
|
2794
|
-
if (!
|
|
2925
|
+
const agentsDir = path5.join(root, "agents");
|
|
2926
|
+
const flatWorkspace = path5.join(root, "workspace");
|
|
2927
|
+
if (!fs4.existsSync(flatWorkspace) || fs4.existsSync(agentsDir))
|
|
2795
2928
|
return;
|
|
2796
2929
|
let ownerAgentId;
|
|
2797
|
-
const sessionsDir =
|
|
2798
|
-
if (
|
|
2930
|
+
const sessionsDir = path5.join(root, "sessions");
|
|
2931
|
+
if (fs4.existsSync(sessionsDir)) {
|
|
2799
2932
|
try {
|
|
2800
|
-
for (const file of
|
|
2933
|
+
for (const file of fs4.readdirSync(sessionsDir)) {
|
|
2801
2934
|
if (!file.endsWith(".json"))
|
|
2802
2935
|
continue;
|
|
2803
2936
|
const decoded = Buffer.from(file.replace(".json", ""), "base64url").toString();
|
|
@@ -2811,13 +2944,13 @@ var DaemonSupervisor = class {
|
|
|
2811
2944
|
}
|
|
2812
2945
|
}
|
|
2813
2946
|
const targetId = ownerAgentId ?? "_orphan";
|
|
2814
|
-
const targetDir =
|
|
2947
|
+
const targetDir = path5.join(agentsDir, targetId);
|
|
2815
2948
|
try {
|
|
2816
|
-
|
|
2949
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
2817
2950
|
for (const sub of ["workspace", "sessions", "dispatch-context"]) {
|
|
2818
|
-
const src =
|
|
2819
|
-
if (
|
|
2820
|
-
|
|
2951
|
+
const src = path5.join(root, sub);
|
|
2952
|
+
if (fs4.existsSync(src)) {
|
|
2953
|
+
fs4.renameSync(src, path5.join(targetDir, sub));
|
|
2821
2954
|
}
|
|
2822
2955
|
}
|
|
2823
2956
|
this.log.info(`migrated legacy flat state \u2192 agents/${targetId}/`);
|
|
@@ -2877,6 +3010,28 @@ var DaemonSupervisor = class {
|
|
|
2877
3010
|
await this.terminateChild(state);
|
|
2878
3011
|
this.children.delete(agentId);
|
|
2879
3012
|
}
|
|
3013
|
+
async handleFilesystemBrowse(requestId, dirPath) {
|
|
3014
|
+
try {
|
|
3015
|
+
const result = await listDirectory(dirPath);
|
|
3016
|
+
await this.client.postBrowseResponse(requestId, {
|
|
3017
|
+
request_id: requestId,
|
|
3018
|
+
path: dirPath,
|
|
3019
|
+
entries: result.entries,
|
|
3020
|
+
error: result.error
|
|
3021
|
+
});
|
|
3022
|
+
} catch (err) {
|
|
3023
|
+
this.log.warn(`filesystem browse failed: ${String(err)}`);
|
|
3024
|
+
try {
|
|
3025
|
+
await this.client.postBrowseResponse(requestId, {
|
|
3026
|
+
request_id: requestId,
|
|
3027
|
+
path: dirPath,
|
|
3028
|
+
entries: [],
|
|
3029
|
+
error: String(err)
|
|
3030
|
+
});
|
|
3031
|
+
} catch {
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
2880
3035
|
async handleWorkspaceSetupRequested(agentId) {
|
|
2881
3036
|
if (this.spawningAgents.has(agentId)) {
|
|
2882
3037
|
this.log.info(`agent ${agentId}: workspace setup already in progress; queueing one restart`);
|
|
@@ -3002,9 +3157,9 @@ var DaemonSupervisor = class {
|
|
|
3002
3157
|
const isK8s = !!process.env.KUBERNETES_SERVICE_HOST;
|
|
3003
3158
|
const claudeHome = isK8s ? agentClaudeHomeFor(this.config.rootClaudeHome, agentId) : this.config.rootClaudeHome;
|
|
3004
3159
|
try {
|
|
3005
|
-
|
|
3160
|
+
fs4.mkdirSync(stateDir, { recursive: true });
|
|
3006
3161
|
if (isK8s) {
|
|
3007
|
-
|
|
3162
|
+
fs4.mkdirSync(claudeHome, { recursive: true });
|
|
3008
3163
|
this.ensureSharedCredentialLink(claudeHome, agentId);
|
|
3009
3164
|
}
|
|
3010
3165
|
} catch (err) {
|
|
@@ -3109,15 +3264,15 @@ var DaemonSupervisor = class {
|
|
|
3109
3264
|
const child = state.child;
|
|
3110
3265
|
if (!child)
|
|
3111
3266
|
return;
|
|
3112
|
-
return new Promise((
|
|
3113
|
-
const onExit = () =>
|
|
3267
|
+
return new Promise((resolve5) => {
|
|
3268
|
+
const onExit = () => resolve5();
|
|
3114
3269
|
child.once("exit", onExit);
|
|
3115
3270
|
try {
|
|
3116
3271
|
child.kill("SIGTERM");
|
|
3117
3272
|
} catch (err) {
|
|
3118
3273
|
this.log.warn(`SIGTERM ${state.agentId} threw: ${String(err)}`);
|
|
3119
3274
|
child.off("exit", onExit);
|
|
3120
|
-
|
|
3275
|
+
resolve5();
|
|
3121
3276
|
return;
|
|
3122
3277
|
}
|
|
3123
3278
|
const hardKill = setTimeout(() => {
|
|
@@ -3130,60 +3285,60 @@ var DaemonSupervisor = class {
|
|
|
3130
3285
|
});
|
|
3131
3286
|
}
|
|
3132
3287
|
ensureSharedCredentialLink(agentClaudeHome, agentId) {
|
|
3133
|
-
const sharedCredentials =
|
|
3288
|
+
const sharedCredentials = path5.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
|
|
3134
3289
|
const agentCredentials = agentClaudeCredentialsFileFor(agentClaudeHome);
|
|
3135
|
-
const agentCredentialsDir =
|
|
3136
|
-
|
|
3137
|
-
|
|
3290
|
+
const agentCredentialsDir = path5.dirname(agentCredentials);
|
|
3291
|
+
fs4.mkdirSync(path5.dirname(sharedCredentials), { recursive: true });
|
|
3292
|
+
fs4.mkdirSync(agentCredentialsDir, { recursive: true });
|
|
3138
3293
|
try {
|
|
3139
|
-
const existing =
|
|
3294
|
+
const existing = fs4.lstatSync(agentCredentials);
|
|
3140
3295
|
if (existing.isSymbolicLink()) {
|
|
3141
|
-
const currentTarget =
|
|
3142
|
-
if (
|
|
3296
|
+
const currentTarget = fs4.readlinkSync(agentCredentials);
|
|
3297
|
+
if (path5.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
|
|
3143
3298
|
return;
|
|
3144
3299
|
}
|
|
3145
|
-
|
|
3300
|
+
fs4.unlinkSync(agentCredentials);
|
|
3146
3301
|
} else if (existing.isDirectory()) {
|
|
3147
3302
|
this.log.warn(`agent ${agentId}: credential path is a directory, cannot link ${agentCredentials}`);
|
|
3148
3303
|
return;
|
|
3149
3304
|
} else {
|
|
3150
|
-
|
|
3305
|
+
fs4.unlinkSync(agentCredentials);
|
|
3151
3306
|
}
|
|
3152
3307
|
} catch (err) {
|
|
3153
3308
|
if (err.code !== "ENOENT") {
|
|
3154
3309
|
throw err;
|
|
3155
3310
|
}
|
|
3156
3311
|
}
|
|
3157
|
-
|
|
3312
|
+
fs4.symlinkSync(sharedCredentials, agentCredentials);
|
|
3158
3313
|
}
|
|
3159
3314
|
};
|
|
3160
3315
|
|
|
3161
3316
|
// ts/daemon/dist/cli.js
|
|
3162
|
-
import * as
|
|
3163
|
-
import * as
|
|
3164
|
-
import * as
|
|
3317
|
+
import * as fs5 from "node:fs";
|
|
3318
|
+
import * as path6 from "node:path";
|
|
3319
|
+
import * as os3 from "node:os";
|
|
3165
3320
|
import * as readline from "node:readline";
|
|
3166
3321
|
import { spawn as spawn3, execSync } from "node:child_process";
|
|
3167
3322
|
var CONFIG_DIR = daemonConfigDir();
|
|
3168
3323
|
var CONFIG_PATH = daemonConfigPath();
|
|
3169
3324
|
function readConfig() {
|
|
3170
3325
|
try {
|
|
3171
|
-
return JSON.parse(
|
|
3326
|
+
return JSON.parse(fs5.readFileSync(CONFIG_PATH, "utf-8"));
|
|
3172
3327
|
} catch {
|
|
3173
3328
|
return null;
|
|
3174
3329
|
}
|
|
3175
3330
|
}
|
|
3176
3331
|
function writeConfig(config) {
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3332
|
+
fs5.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
3333
|
+
fs5.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
|
|
3334
|
+
fs5.chmodSync(CONFIG_PATH, 384);
|
|
3180
3335
|
}
|
|
3181
3336
|
function prompt(question) {
|
|
3182
3337
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3183
|
-
return new Promise((
|
|
3338
|
+
return new Promise((resolve5) => {
|
|
3184
3339
|
rl.question(question, (answer) => {
|
|
3185
3340
|
rl.close();
|
|
3186
|
-
|
|
3341
|
+
resolve5(answer.trim());
|
|
3187
3342
|
});
|
|
3188
3343
|
});
|
|
3189
3344
|
}
|
|
@@ -3195,10 +3350,10 @@ function isLinux() {
|
|
|
3195
3350
|
}
|
|
3196
3351
|
var PLIST_LABEL = "com.parall.daemon";
|
|
3197
3352
|
function plistPath() {
|
|
3198
|
-
return
|
|
3353
|
+
return path6.join(os3.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
3199
3354
|
}
|
|
3200
3355
|
function systemdUnitPath() {
|
|
3201
|
-
return
|
|
3356
|
+
return path6.join(os3.homedir(), ".config", "systemd", "user", "parall-daemon.service");
|
|
3202
3357
|
}
|
|
3203
3358
|
function getDaemonBin() {
|
|
3204
3359
|
try {
|
|
@@ -3208,7 +3363,7 @@ function getDaemonBin() {
|
|
|
3208
3363
|
}
|
|
3209
3364
|
}
|
|
3210
3365
|
function generatePlist(daemonBin) {
|
|
3211
|
-
const logPath =
|
|
3366
|
+
const logPath = path6.join(os3.homedir(), "Library", "Logs", "parall-daemon.log");
|
|
3212
3367
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3213
3368
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3214
3369
|
<plist version="1.0">
|
|
@@ -3255,16 +3410,16 @@ function installService() {
|
|
|
3255
3410
|
}
|
|
3256
3411
|
const bin = getDaemonBin();
|
|
3257
3412
|
if (isMacOS()) {
|
|
3258
|
-
const dir =
|
|
3259
|
-
|
|
3260
|
-
|
|
3413
|
+
const dir = path6.dirname(plistPath());
|
|
3414
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
3415
|
+
fs5.writeFileSync(plistPath(), generatePlist(bin));
|
|
3261
3416
|
execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
|
|
3262
3417
|
execSync(`launchctl bootstrap gui/$(id -u) ${plistPath()}`);
|
|
3263
3418
|
console.log(`launchd agent installed: ${plistPath()}`);
|
|
3264
3419
|
} else if (isLinux()) {
|
|
3265
|
-
const dir =
|
|
3266
|
-
|
|
3267
|
-
|
|
3420
|
+
const dir = path6.dirname(systemdUnitPath());
|
|
3421
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
3422
|
+
fs5.writeFileSync(systemdUnitPath(), generateSystemdUnit(bin));
|
|
3268
3423
|
execSync("systemctl --user daemon-reload");
|
|
3269
3424
|
execSync("systemctl --user enable --now parall-daemon");
|
|
3270
3425
|
console.log(`systemd service installed: ${systemdUnitPath()}`);
|
|
@@ -3331,8 +3486,8 @@ function cmdLogs(lines) {
|
|
|
3331
3486
|
child2.on("exit", (code) => process.exit(code ?? 0));
|
|
3332
3487
|
return;
|
|
3333
3488
|
}
|
|
3334
|
-
const logPath =
|
|
3335
|
-
if (!
|
|
3489
|
+
const logPath = path6.join(os3.homedir(), "Library", "Logs", "parall-daemon.log");
|
|
3490
|
+
if (!fs5.existsSync(logPath)) {
|
|
3336
3491
|
console.log("No log file found at", logPath);
|
|
3337
3492
|
return;
|
|
3338
3493
|
}
|
|
@@ -3342,14 +3497,14 @@ function cmdLogs(lines) {
|
|
|
3342
3497
|
function cmdServiceUninstall() {
|
|
3343
3498
|
if (isMacOS()) {
|
|
3344
3499
|
execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
|
|
3345
|
-
if (
|
|
3346
|
-
|
|
3500
|
+
if (fs5.existsSync(plistPath()))
|
|
3501
|
+
fs5.unlinkSync(plistPath());
|
|
3347
3502
|
console.log("launchd agent uninstalled.");
|
|
3348
3503
|
} else if (isLinux()) {
|
|
3349
3504
|
execSync("systemctl --user stop parall-daemon 2>/dev/null || true");
|
|
3350
3505
|
execSync("systemctl --user disable parall-daemon 2>/dev/null || true");
|
|
3351
|
-
if (
|
|
3352
|
-
|
|
3506
|
+
if (fs5.existsSync(systemdUnitPath()))
|
|
3507
|
+
fs5.unlinkSync(systemdUnitPath());
|
|
3353
3508
|
execSync("systemctl --user daemon-reload");
|
|
3354
3509
|
console.log("systemd service uninstalled.");
|
|
3355
3510
|
} else {
|