@parall/daemon 1.29.2 → 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 +261 -105
- 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/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +2 -1
- 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,14 +2268,15 @@ 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) {
|
|
2147
2276
|
const prior = opts.attached.workspace_state;
|
|
2148
2277
|
const plan = buildWorkspacePlan(opts.attached.daemon_config, opts.defaultWorkspaceDir, prior?.config_hash);
|
|
2149
|
-
|
|
2278
|
+
const hasConfig = opts.attached.daemon_config && Object.keys(opts.attached.daemon_config).length > 0;
|
|
2279
|
+
if (!hasConfig && !prior) {
|
|
2150
2280
|
return plan.workspaceDir;
|
|
2151
2281
|
}
|
|
2152
2282
|
let forceSetup = false;
|
|
@@ -2256,7 +2386,7 @@ function resolveWorkspaceDir(workspace, defaultWorkspaceDir) {
|
|
|
2256
2386
|
async function ensureWorkspace(plan, log2) {
|
|
2257
2387
|
const ws = plan.workspace;
|
|
2258
2388
|
if (ws.mode === "default") {
|
|
2259
|
-
|
|
2389
|
+
fs3.mkdirSync(plan.workspaceDir, { recursive: true });
|
|
2260
2390
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2261
2391
|
return;
|
|
2262
2392
|
}
|
|
@@ -2264,7 +2394,7 @@ async function ensureWorkspace(plan, log2) {
|
|
|
2264
2394
|
assertSafeCustomWorkspacePath(plan);
|
|
2265
2395
|
let st;
|
|
2266
2396
|
try {
|
|
2267
|
-
st =
|
|
2397
|
+
st = fs3.statSync(plan.workspaceDir);
|
|
2268
2398
|
} catch (err) {
|
|
2269
2399
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
2270
2400
|
throw new Error(`workspace path does not exist: ${plan.workspaceDir}`);
|
|
@@ -2274,7 +2404,7 @@ async function ensureWorkspace(plan, log2) {
|
|
|
2274
2404
|
if (!st.isDirectory()) {
|
|
2275
2405
|
throw new Error(`workspace path is not a directory: ${plan.workspaceDir}`);
|
|
2276
2406
|
}
|
|
2277
|
-
assertSafeCustomWorkspacePath(plan,
|
|
2407
|
+
assertSafeCustomWorkspacePath(plan, fs3.realpathSync(plan.workspaceDir));
|
|
2278
2408
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2279
2409
|
return;
|
|
2280
2410
|
}
|
|
@@ -2285,17 +2415,17 @@ async function ensureWorkspace(plan, log2) {
|
|
|
2285
2415
|
if (plan.customWorkspaceField) {
|
|
2286
2416
|
assertSafeCustomWorkspacePath(plan);
|
|
2287
2417
|
}
|
|
2288
|
-
if (!
|
|
2289
|
-
|
|
2290
|
-
assertWritableWorkspaceDir(
|
|
2418
|
+
if (!fs3.existsSync(plan.workspaceDir)) {
|
|
2419
|
+
fs3.mkdirSync(path4.dirname(plan.workspaceDir), { recursive: true });
|
|
2420
|
+
assertWritableWorkspaceDir(path4.dirname(plan.workspaceDir));
|
|
2291
2421
|
await runCommand("git", ["clone", remote, plan.workspaceDir], process.cwd());
|
|
2292
2422
|
} else {
|
|
2293
|
-
const st =
|
|
2423
|
+
const st = fs3.statSync(plan.workspaceDir);
|
|
2294
2424
|
if (!st.isDirectory()) {
|
|
2295
2425
|
throw new Error(`workspace path is not a directory: ${plan.workspaceDir}`);
|
|
2296
2426
|
}
|
|
2297
2427
|
if (plan.customWorkspaceField) {
|
|
2298
|
-
assertSafeCustomWorkspacePath(plan,
|
|
2428
|
+
assertSafeCustomWorkspacePath(plan, fs3.realpathSync(plan.workspaceDir));
|
|
2299
2429
|
}
|
|
2300
2430
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2301
2431
|
await ensureGitWorktree(plan.workspaceDir);
|
|
@@ -2319,13 +2449,13 @@ async function verifyExistingWorkspace(plan, log2) {
|
|
|
2319
2449
|
if (plan.customWorkspaceField) {
|
|
2320
2450
|
assertSafeCustomWorkspacePath(plan);
|
|
2321
2451
|
}
|
|
2322
|
-
const st =
|
|
2452
|
+
const st = fs3.statSync(plan.workspaceDir);
|
|
2323
2453
|
if (!st.isDirectory()) {
|
|
2324
2454
|
log2.warn(`workspace ready state ignored: path is not a directory: ${plan.workspaceDir}`);
|
|
2325
2455
|
return false;
|
|
2326
2456
|
}
|
|
2327
2457
|
if (plan.customWorkspaceField) {
|
|
2328
|
-
assertSafeCustomWorkspacePath(plan,
|
|
2458
|
+
assertSafeCustomWorkspacePath(plan, fs3.realpathSync(plan.workspaceDir));
|
|
2329
2459
|
}
|
|
2330
2460
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2331
2461
|
if (plan.workspace.mode === "git") {
|
|
@@ -2418,7 +2548,7 @@ async function tryGitOutput(cmd, args, cwd) {
|
|
|
2418
2548
|
}
|
|
2419
2549
|
}
|
|
2420
2550
|
function runCommand(cmd, args, cwd, timeoutMs = 12e4, env = process.env) {
|
|
2421
|
-
return new Promise((
|
|
2551
|
+
return new Promise((resolve5, reject) => {
|
|
2422
2552
|
let tail = "";
|
|
2423
2553
|
let timedOut = false;
|
|
2424
2554
|
let settled = false;
|
|
@@ -2460,7 +2590,7 @@ ${tail}`)));
|
|
|
2460
2590
|
return;
|
|
2461
2591
|
}
|
|
2462
2592
|
if (code === 0) {
|
|
2463
|
-
settle(() =>
|
|
2593
|
+
settle(() => resolve5(tail));
|
|
2464
2594
|
} else {
|
|
2465
2595
|
settle(() => reject(new Error(`command failed (${code ?? signal}): ${cmd} ${args.join(" ")}
|
|
2466
2596
|
${tail}`)));
|
|
@@ -2469,10 +2599,10 @@ ${tail}`)));
|
|
|
2469
2599
|
});
|
|
2470
2600
|
}
|
|
2471
2601
|
function requireAbsolute(value, field) {
|
|
2472
|
-
if (!value || !
|
|
2602
|
+
if (!value || !path4.isAbsolute(value)) {
|
|
2473
2603
|
throw new Error(`${field} must be an absolute path`);
|
|
2474
2604
|
}
|
|
2475
|
-
return
|
|
2605
|
+
return path4.resolve(value);
|
|
2476
2606
|
}
|
|
2477
2607
|
function assertSafeCustomWorkspacePath(plan, candidate = plan.workspaceDir) {
|
|
2478
2608
|
if (!plan.customWorkspaceField)
|
|
@@ -2482,17 +2612,17 @@ function assertSafeCustomWorkspacePath(plan, candidate = plan.workspaceDir) {
|
|
|
2482
2612
|
if (reason) {
|
|
2483
2613
|
throw new Error(`${plan.customWorkspaceField} must point to a project folder, not ${reason}: ${normalized}`);
|
|
2484
2614
|
}
|
|
2485
|
-
const defaultWorkspace =
|
|
2615
|
+
const defaultWorkspace = path4.resolve(plan.defaultWorkspaceDir);
|
|
2486
2616
|
if (isAncestorPath(normalized, defaultWorkspace) || normalized === defaultWorkspace) {
|
|
2487
2617
|
throw new Error(`${plan.customWorkspaceField} must not point at daemon state directories: ${normalized}`);
|
|
2488
2618
|
}
|
|
2489
2619
|
}
|
|
2490
2620
|
function assertWritableWorkspaceDir(dir) {
|
|
2491
|
-
|
|
2492
|
-
const probe =
|
|
2493
|
-
const fd =
|
|
2494
|
-
|
|
2495
|
-
|
|
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);
|
|
2496
2626
|
}
|
|
2497
2627
|
function workspacePathDenyReason(value) {
|
|
2498
2628
|
if (value === "/")
|
|
@@ -2553,11 +2683,11 @@ function workspacePathDenyReason(value) {
|
|
|
2553
2683
|
return "";
|
|
2554
2684
|
}
|
|
2555
2685
|
function isAncestorPath(parent, child) {
|
|
2556
|
-
const relative2 =
|
|
2557
|
-
return relative2 !== "" && !relative2.startsWith("..") && !
|
|
2686
|
+
const relative2 = path4.relative(parent, child);
|
|
2687
|
+
return relative2 !== "" && !relative2.startsWith("..") && !path4.isAbsolute(relative2);
|
|
2558
2688
|
}
|
|
2559
2689
|
function toPolicyPath(value) {
|
|
2560
|
-
return
|
|
2690
|
+
return path4.resolve(value).split(path4.sep).join("/");
|
|
2561
2691
|
}
|
|
2562
2692
|
function isNodeError(err) {
|
|
2563
2693
|
return err instanceof Error && "code" in err;
|
|
@@ -2573,14 +2703,14 @@ var WORKSPACE_SETUP_RETRY_DELAY_MS = 5e3;
|
|
|
2573
2703
|
function sleepCancellable(ms, signal) {
|
|
2574
2704
|
if (signal.aborted)
|
|
2575
2705
|
return Promise.resolve(false);
|
|
2576
|
-
return new Promise((
|
|
2706
|
+
return new Promise((resolve5) => {
|
|
2577
2707
|
const timer = setTimeout(() => {
|
|
2578
2708
|
signal.removeEventListener("abort", onAbort);
|
|
2579
|
-
|
|
2709
|
+
resolve5(true);
|
|
2580
2710
|
}, ms);
|
|
2581
2711
|
const onAbort = () => {
|
|
2582
2712
|
clearTimeout(timer);
|
|
2583
|
-
|
|
2713
|
+
resolve5(false);
|
|
2584
2714
|
};
|
|
2585
2715
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2586
2716
|
});
|
|
@@ -2658,6 +2788,10 @@ var DaemonSupervisor = class {
|
|
|
2658
2788
|
this.log.info(`WS: workspace setup requested for agent ${data.agent_id}`);
|
|
2659
2789
|
void this.handleWorkspaceSetupRequested(data.agent_id);
|
|
2660
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
|
+
});
|
|
2661
2795
|
this.ws.on("machine.stop", (data) => {
|
|
2662
2796
|
this.log.info(`WS: machine.stop received (reason=${data.reason ?? "none"})`);
|
|
2663
2797
|
void this.stop();
|
|
@@ -2668,8 +2802,8 @@ var DaemonSupervisor = class {
|
|
|
2668
2802
|
}
|
|
2669
2803
|
});
|
|
2670
2804
|
await this.ws.connect();
|
|
2671
|
-
await new Promise((
|
|
2672
|
-
this.stopResolve =
|
|
2805
|
+
await new Promise((resolve5) => {
|
|
2806
|
+
this.stopResolve = resolve5;
|
|
2673
2807
|
});
|
|
2674
2808
|
signal.removeEventListener("abort", onAbort);
|
|
2675
2809
|
}
|
|
@@ -2788,15 +2922,15 @@ var DaemonSupervisor = class {
|
|
|
2788
2922
|
*/
|
|
2789
2923
|
migrateFlatLayout() {
|
|
2790
2924
|
const root = this.config.rootStateDir;
|
|
2791
|
-
const agentsDir =
|
|
2792
|
-
const flatWorkspace =
|
|
2793
|
-
if (!
|
|
2925
|
+
const agentsDir = path5.join(root, "agents");
|
|
2926
|
+
const flatWorkspace = path5.join(root, "workspace");
|
|
2927
|
+
if (!fs4.existsSync(flatWorkspace) || fs4.existsSync(agentsDir))
|
|
2794
2928
|
return;
|
|
2795
2929
|
let ownerAgentId;
|
|
2796
|
-
const sessionsDir =
|
|
2797
|
-
if (
|
|
2930
|
+
const sessionsDir = path5.join(root, "sessions");
|
|
2931
|
+
if (fs4.existsSync(sessionsDir)) {
|
|
2798
2932
|
try {
|
|
2799
|
-
for (const file of
|
|
2933
|
+
for (const file of fs4.readdirSync(sessionsDir)) {
|
|
2800
2934
|
if (!file.endsWith(".json"))
|
|
2801
2935
|
continue;
|
|
2802
2936
|
const decoded = Buffer.from(file.replace(".json", ""), "base64url").toString();
|
|
@@ -2810,13 +2944,13 @@ var DaemonSupervisor = class {
|
|
|
2810
2944
|
}
|
|
2811
2945
|
}
|
|
2812
2946
|
const targetId = ownerAgentId ?? "_orphan";
|
|
2813
|
-
const targetDir =
|
|
2947
|
+
const targetDir = path5.join(agentsDir, targetId);
|
|
2814
2948
|
try {
|
|
2815
|
-
|
|
2949
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
2816
2950
|
for (const sub of ["workspace", "sessions", "dispatch-context"]) {
|
|
2817
|
-
const src =
|
|
2818
|
-
if (
|
|
2819
|
-
|
|
2951
|
+
const src = path5.join(root, sub);
|
|
2952
|
+
if (fs4.existsSync(src)) {
|
|
2953
|
+
fs4.renameSync(src, path5.join(targetDir, sub));
|
|
2820
2954
|
}
|
|
2821
2955
|
}
|
|
2822
2956
|
this.log.info(`migrated legacy flat state \u2192 agents/${targetId}/`);
|
|
@@ -2876,6 +3010,28 @@ var DaemonSupervisor = class {
|
|
|
2876
3010
|
await this.terminateChild(state);
|
|
2877
3011
|
this.children.delete(agentId);
|
|
2878
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
|
+
}
|
|
2879
3035
|
async handleWorkspaceSetupRequested(agentId) {
|
|
2880
3036
|
if (this.spawningAgents.has(agentId)) {
|
|
2881
3037
|
this.log.info(`agent ${agentId}: workspace setup already in progress; queueing one restart`);
|
|
@@ -3001,9 +3157,9 @@ var DaemonSupervisor = class {
|
|
|
3001
3157
|
const isK8s = !!process.env.KUBERNETES_SERVICE_HOST;
|
|
3002
3158
|
const claudeHome = isK8s ? agentClaudeHomeFor(this.config.rootClaudeHome, agentId) : this.config.rootClaudeHome;
|
|
3003
3159
|
try {
|
|
3004
|
-
|
|
3160
|
+
fs4.mkdirSync(stateDir, { recursive: true });
|
|
3005
3161
|
if (isK8s) {
|
|
3006
|
-
|
|
3162
|
+
fs4.mkdirSync(claudeHome, { recursive: true });
|
|
3007
3163
|
this.ensureSharedCredentialLink(claudeHome, agentId);
|
|
3008
3164
|
}
|
|
3009
3165
|
} catch (err) {
|
|
@@ -3108,15 +3264,15 @@ var DaemonSupervisor = class {
|
|
|
3108
3264
|
const child = state.child;
|
|
3109
3265
|
if (!child)
|
|
3110
3266
|
return;
|
|
3111
|
-
return new Promise((
|
|
3112
|
-
const onExit = () =>
|
|
3267
|
+
return new Promise((resolve5) => {
|
|
3268
|
+
const onExit = () => resolve5();
|
|
3113
3269
|
child.once("exit", onExit);
|
|
3114
3270
|
try {
|
|
3115
3271
|
child.kill("SIGTERM");
|
|
3116
3272
|
} catch (err) {
|
|
3117
3273
|
this.log.warn(`SIGTERM ${state.agentId} threw: ${String(err)}`);
|
|
3118
3274
|
child.off("exit", onExit);
|
|
3119
|
-
|
|
3275
|
+
resolve5();
|
|
3120
3276
|
return;
|
|
3121
3277
|
}
|
|
3122
3278
|
const hardKill = setTimeout(() => {
|
|
@@ -3129,60 +3285,60 @@ var DaemonSupervisor = class {
|
|
|
3129
3285
|
});
|
|
3130
3286
|
}
|
|
3131
3287
|
ensureSharedCredentialLink(agentClaudeHome, agentId) {
|
|
3132
|
-
const sharedCredentials =
|
|
3288
|
+
const sharedCredentials = path5.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
|
|
3133
3289
|
const agentCredentials = agentClaudeCredentialsFileFor(agentClaudeHome);
|
|
3134
|
-
const agentCredentialsDir =
|
|
3135
|
-
|
|
3136
|
-
|
|
3290
|
+
const agentCredentialsDir = path5.dirname(agentCredentials);
|
|
3291
|
+
fs4.mkdirSync(path5.dirname(sharedCredentials), { recursive: true });
|
|
3292
|
+
fs4.mkdirSync(agentCredentialsDir, { recursive: true });
|
|
3137
3293
|
try {
|
|
3138
|
-
const existing =
|
|
3294
|
+
const existing = fs4.lstatSync(agentCredentials);
|
|
3139
3295
|
if (existing.isSymbolicLink()) {
|
|
3140
|
-
const currentTarget =
|
|
3141
|
-
if (
|
|
3296
|
+
const currentTarget = fs4.readlinkSync(agentCredentials);
|
|
3297
|
+
if (path5.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
|
|
3142
3298
|
return;
|
|
3143
3299
|
}
|
|
3144
|
-
|
|
3300
|
+
fs4.unlinkSync(agentCredentials);
|
|
3145
3301
|
} else if (existing.isDirectory()) {
|
|
3146
3302
|
this.log.warn(`agent ${agentId}: credential path is a directory, cannot link ${agentCredentials}`);
|
|
3147
3303
|
return;
|
|
3148
3304
|
} else {
|
|
3149
|
-
|
|
3305
|
+
fs4.unlinkSync(agentCredentials);
|
|
3150
3306
|
}
|
|
3151
3307
|
} catch (err) {
|
|
3152
3308
|
if (err.code !== "ENOENT") {
|
|
3153
3309
|
throw err;
|
|
3154
3310
|
}
|
|
3155
3311
|
}
|
|
3156
|
-
|
|
3312
|
+
fs4.symlinkSync(sharedCredentials, agentCredentials);
|
|
3157
3313
|
}
|
|
3158
3314
|
};
|
|
3159
3315
|
|
|
3160
3316
|
// ts/daemon/dist/cli.js
|
|
3161
|
-
import * as
|
|
3162
|
-
import * as
|
|
3163
|
-
import * as
|
|
3317
|
+
import * as fs5 from "node:fs";
|
|
3318
|
+
import * as path6 from "node:path";
|
|
3319
|
+
import * as os3 from "node:os";
|
|
3164
3320
|
import * as readline from "node:readline";
|
|
3165
3321
|
import { spawn as spawn3, execSync } from "node:child_process";
|
|
3166
3322
|
var CONFIG_DIR = daemonConfigDir();
|
|
3167
3323
|
var CONFIG_PATH = daemonConfigPath();
|
|
3168
3324
|
function readConfig() {
|
|
3169
3325
|
try {
|
|
3170
|
-
return JSON.parse(
|
|
3326
|
+
return JSON.parse(fs5.readFileSync(CONFIG_PATH, "utf-8"));
|
|
3171
3327
|
} catch {
|
|
3172
3328
|
return null;
|
|
3173
3329
|
}
|
|
3174
3330
|
}
|
|
3175
3331
|
function writeConfig(config) {
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
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);
|
|
3179
3335
|
}
|
|
3180
3336
|
function prompt(question) {
|
|
3181
3337
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3182
|
-
return new Promise((
|
|
3338
|
+
return new Promise((resolve5) => {
|
|
3183
3339
|
rl.question(question, (answer) => {
|
|
3184
3340
|
rl.close();
|
|
3185
|
-
|
|
3341
|
+
resolve5(answer.trim());
|
|
3186
3342
|
});
|
|
3187
3343
|
});
|
|
3188
3344
|
}
|
|
@@ -3194,10 +3350,10 @@ function isLinux() {
|
|
|
3194
3350
|
}
|
|
3195
3351
|
var PLIST_LABEL = "com.parall.daemon";
|
|
3196
3352
|
function plistPath() {
|
|
3197
|
-
return
|
|
3353
|
+
return path6.join(os3.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
3198
3354
|
}
|
|
3199
3355
|
function systemdUnitPath() {
|
|
3200
|
-
return
|
|
3356
|
+
return path6.join(os3.homedir(), ".config", "systemd", "user", "parall-daemon.service");
|
|
3201
3357
|
}
|
|
3202
3358
|
function getDaemonBin() {
|
|
3203
3359
|
try {
|
|
@@ -3207,7 +3363,7 @@ function getDaemonBin() {
|
|
|
3207
3363
|
}
|
|
3208
3364
|
}
|
|
3209
3365
|
function generatePlist(daemonBin) {
|
|
3210
|
-
const logPath =
|
|
3366
|
+
const logPath = path6.join(os3.homedir(), "Library", "Logs", "parall-daemon.log");
|
|
3211
3367
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3212
3368
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3213
3369
|
<plist version="1.0">
|
|
@@ -3254,16 +3410,16 @@ function installService() {
|
|
|
3254
3410
|
}
|
|
3255
3411
|
const bin = getDaemonBin();
|
|
3256
3412
|
if (isMacOS()) {
|
|
3257
|
-
const dir =
|
|
3258
|
-
|
|
3259
|
-
|
|
3413
|
+
const dir = path6.dirname(plistPath());
|
|
3414
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
3415
|
+
fs5.writeFileSync(plistPath(), generatePlist(bin));
|
|
3260
3416
|
execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
|
|
3261
3417
|
execSync(`launchctl bootstrap gui/$(id -u) ${plistPath()}`);
|
|
3262
3418
|
console.log(`launchd agent installed: ${plistPath()}`);
|
|
3263
3419
|
} else if (isLinux()) {
|
|
3264
|
-
const dir =
|
|
3265
|
-
|
|
3266
|
-
|
|
3420
|
+
const dir = path6.dirname(systemdUnitPath());
|
|
3421
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
3422
|
+
fs5.writeFileSync(systemdUnitPath(), generateSystemdUnit(bin));
|
|
3267
3423
|
execSync("systemctl --user daemon-reload");
|
|
3268
3424
|
execSync("systemctl --user enable --now parall-daemon");
|
|
3269
3425
|
console.log(`systemd service installed: ${systemdUnitPath()}`);
|
|
@@ -3330,8 +3486,8 @@ function cmdLogs(lines) {
|
|
|
3330
3486
|
child2.on("exit", (code) => process.exit(code ?? 0));
|
|
3331
3487
|
return;
|
|
3332
3488
|
}
|
|
3333
|
-
const logPath =
|
|
3334
|
-
if (!
|
|
3489
|
+
const logPath = path6.join(os3.homedir(), "Library", "Logs", "parall-daemon.log");
|
|
3490
|
+
if (!fs5.existsSync(logPath)) {
|
|
3335
3491
|
console.log("No log file found at", logPath);
|
|
3336
3492
|
return;
|
|
3337
3493
|
}
|
|
@@ -3341,14 +3497,14 @@ function cmdLogs(lines) {
|
|
|
3341
3497
|
function cmdServiceUninstall() {
|
|
3342
3498
|
if (isMacOS()) {
|
|
3343
3499
|
execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
|
|
3344
|
-
if (
|
|
3345
|
-
|
|
3500
|
+
if (fs5.existsSync(plistPath()))
|
|
3501
|
+
fs5.unlinkSync(plistPath());
|
|
3346
3502
|
console.log("launchd agent uninstalled.");
|
|
3347
3503
|
} else if (isLinux()) {
|
|
3348
3504
|
execSync("systemctl --user stop parall-daemon 2>/dev/null || true");
|
|
3349
3505
|
execSync("systemctl --user disable parall-daemon 2>/dev/null || true");
|
|
3350
|
-
if (
|
|
3351
|
-
|
|
3506
|
+
if (fs5.existsSync(systemdUnitPath()))
|
|
3507
|
+
fs5.unlinkSync(systemdUnitPath());
|
|
3352
3508
|
execSync("systemctl --user daemon-reload");
|
|
3353
3509
|
console.log("systemd service uninstalled.");
|
|
3354
3510
|
} else {
|