@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.
@@ -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(path6) {
400
+ async ensureFreshToken(path7) {
395
401
  if (!this.token || !this.getRefreshToken)
396
402
  return;
397
- const pathSuffix = path6.replace(/^\/api\/v1/, "");
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, path6, body, query, retried = false, opts) {
435
+ async request(method, path7, body, query, retried = false, opts) {
430
436
  if (!retried) {
431
- await this.ensureFreshToken(path6);
437
+ await this.ensureFreshToken(path7);
432
438
  }
433
- let url = `${this.baseUrl}${path6}`;
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 = path6.replace(/^\/api\/v1/, "");
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, path6, body, query, true, opts);
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, path6, body, retried = false) {
506
+ async multipartRequest(method, path7, body, retried = false) {
501
507
  if (!retried) {
502
- await this.ensureFreshToken(path6);
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}${path6}`, {
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 = path6.replace(/^\/api\/v1/, "");
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, path6, body, true);
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, path6) {
1408
- return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path6 ? { path: path6 } : 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, path6, params) {
1418
- return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, { path: path6, ...params });
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, path6, ref) {
1421
- return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path6, ref });
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 fs3 from "node:fs";
2020
- import * as path4 from "node:path";
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 path2 from "node:path";
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 = path2.join(dirs.stateDir, ".codex");
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 fs2 from "node:fs";
2143
- import * as path3 from "node:path";
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
- fs2.mkdirSync(plan.workspaceDir, { recursive: true });
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 = fs2.statSync(plan.workspaceDir);
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, fs2.realpathSync(plan.workspaceDir));
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 (!fs2.existsSync(plan.workspaceDir)) {
2290
- fs2.mkdirSync(path3.dirname(plan.workspaceDir), { recursive: true });
2291
- assertWritableWorkspaceDir(path3.dirname(plan.workspaceDir));
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 = fs2.statSync(plan.workspaceDir);
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, fs2.realpathSync(plan.workspaceDir));
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 = fs2.statSync(plan.workspaceDir);
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, fs2.realpathSync(plan.workspaceDir));
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((resolve4, reject) => {
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(() => resolve4(tail));
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 || !path3.isAbsolute(value)) {
2602
+ if (!value || !path4.isAbsolute(value)) {
2474
2603
  throw new Error(`${field} must be an absolute path`);
2475
2604
  }
2476
- return path3.resolve(value);
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 = path3.resolve(plan.defaultWorkspaceDir);
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
- fs2.accessSync(dir, fs2.constants.R_OK | fs2.constants.W_OK | fs2.constants.X_OK);
2493
- const probe = path3.join(dir, `.parall-workspace-check-${process.pid}-${Date.now()}`);
2494
- const fd = fs2.openSync(probe, "wx", 384);
2495
- fs2.closeSync(fd);
2496
- fs2.unlinkSync(probe);
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 = path3.relative(parent, child);
2558
- return relative2 !== "" && !relative2.startsWith("..") && !path3.isAbsolute(relative2);
2686
+ const relative2 = path4.relative(parent, child);
2687
+ return relative2 !== "" && !relative2.startsWith("..") && !path4.isAbsolute(relative2);
2559
2688
  }
2560
2689
  function toPolicyPath(value) {
2561
- return path3.resolve(value).split(path3.sep).join("/");
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((resolve4) => {
2706
+ return new Promise((resolve5) => {
2578
2707
  const timer = setTimeout(() => {
2579
2708
  signal.removeEventListener("abort", onAbort);
2580
- resolve4(true);
2709
+ resolve5(true);
2581
2710
  }, ms);
2582
2711
  const onAbort = () => {
2583
2712
  clearTimeout(timer);
2584
- resolve4(false);
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((resolve4) => {
2673
- this.stopResolve = resolve4;
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 = path4.join(root, "agents");
2793
- const flatWorkspace = path4.join(root, "workspace");
2794
- if (!fs3.existsSync(flatWorkspace) || fs3.existsSync(agentsDir))
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 = path4.join(root, "sessions");
2798
- if (fs3.existsSync(sessionsDir)) {
2930
+ const sessionsDir = path5.join(root, "sessions");
2931
+ if (fs4.existsSync(sessionsDir)) {
2799
2932
  try {
2800
- for (const file of fs3.readdirSync(sessionsDir)) {
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 = path4.join(agentsDir, targetId);
2947
+ const targetDir = path5.join(agentsDir, targetId);
2815
2948
  try {
2816
- fs3.mkdirSync(targetDir, { recursive: true });
2949
+ fs4.mkdirSync(targetDir, { recursive: true });
2817
2950
  for (const sub of ["workspace", "sessions", "dispatch-context"]) {
2818
- const src = path4.join(root, sub);
2819
- if (fs3.existsSync(src)) {
2820
- fs3.renameSync(src, path4.join(targetDir, sub));
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
- fs3.mkdirSync(stateDir, { recursive: true });
3160
+ fs4.mkdirSync(stateDir, { recursive: true });
3006
3161
  if (isK8s) {
3007
- fs3.mkdirSync(claudeHome, { recursive: true });
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((resolve4) => {
3113
- const onExit = () => resolve4();
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
- resolve4();
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 = path4.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
3288
+ const sharedCredentials = path5.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
3134
3289
  const agentCredentials = agentClaudeCredentialsFileFor(agentClaudeHome);
3135
- const agentCredentialsDir = path4.dirname(agentCredentials);
3136
- fs3.mkdirSync(path4.dirname(sharedCredentials), { recursive: true });
3137
- fs3.mkdirSync(agentCredentialsDir, { recursive: true });
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 = fs3.lstatSync(agentCredentials);
3294
+ const existing = fs4.lstatSync(agentCredentials);
3140
3295
  if (existing.isSymbolicLink()) {
3141
- const currentTarget = fs3.readlinkSync(agentCredentials);
3142
- if (path4.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
3296
+ const currentTarget = fs4.readlinkSync(agentCredentials);
3297
+ if (path5.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
3143
3298
  return;
3144
3299
  }
3145
- fs3.unlinkSync(agentCredentials);
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
- fs3.unlinkSync(agentCredentials);
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
- fs3.symlinkSync(sharedCredentials, agentCredentials);
3312
+ fs4.symlinkSync(sharedCredentials, agentCredentials);
3158
3313
  }
3159
3314
  };
3160
3315
 
3161
3316
  // ts/daemon/dist/cli.js
3162
- import * as fs4 from "node:fs";
3163
- import * as path5 from "node:path";
3164
- import * as os2 from "node:os";
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(fs4.readFileSync(CONFIG_PATH, "utf-8"));
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
- fs4.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
3178
- fs4.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
3179
- fs4.chmodSync(CONFIG_PATH, 384);
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((resolve4) => {
3338
+ return new Promise((resolve5) => {
3184
3339
  rl.question(question, (answer) => {
3185
3340
  rl.close();
3186
- resolve4(answer.trim());
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 path5.join(os2.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
3353
+ return path6.join(os3.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
3199
3354
  }
3200
3355
  function systemdUnitPath() {
3201
- return path5.join(os2.homedir(), ".config", "systemd", "user", "parall-daemon.service");
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 = path5.join(os2.homedir(), "Library", "Logs", "parall-daemon.log");
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 = path5.dirname(plistPath());
3259
- fs4.mkdirSync(dir, { recursive: true });
3260
- fs4.writeFileSync(plistPath(), generatePlist(bin));
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 = path5.dirname(systemdUnitPath());
3266
- fs4.mkdirSync(dir, { recursive: true });
3267
- fs4.writeFileSync(systemdUnitPath(), generateSystemdUnit(bin));
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 = path5.join(os2.homedir(), "Library", "Logs", "parall-daemon.log");
3335
- if (!fs4.existsSync(logPath)) {
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 (fs4.existsSync(plistPath()))
3346
- fs4.unlinkSync(plistPath());
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 (fs4.existsSync(systemdUnitPath()))
3352
- fs4.unlinkSync(systemdUnitPath());
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 {