@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.
@@ -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,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 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) {
2147
2276
  const prior = opts.attached.workspace_state;
2148
2277
  const plan = buildWorkspacePlan(opts.attached.daemon_config, opts.defaultWorkspaceDir, prior?.config_hash);
2149
- if (!opts.attached.daemon_config && !prior) {
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
- fs2.mkdirSync(plan.workspaceDir, { recursive: true });
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 = fs2.statSync(plan.workspaceDir);
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, fs2.realpathSync(plan.workspaceDir));
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 (!fs2.existsSync(plan.workspaceDir)) {
2289
- fs2.mkdirSync(path3.dirname(plan.workspaceDir), { recursive: true });
2290
- 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));
2291
2421
  await runCommand("git", ["clone", remote, plan.workspaceDir], process.cwd());
2292
2422
  } else {
2293
- const st = fs2.statSync(plan.workspaceDir);
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, fs2.realpathSync(plan.workspaceDir));
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 = fs2.statSync(plan.workspaceDir);
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, fs2.realpathSync(plan.workspaceDir));
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((resolve4, reject) => {
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(() => resolve4(tail));
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 || !path3.isAbsolute(value)) {
2602
+ if (!value || !path4.isAbsolute(value)) {
2473
2603
  throw new Error(`${field} must be an absolute path`);
2474
2604
  }
2475
- return path3.resolve(value);
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 = path3.resolve(plan.defaultWorkspaceDir);
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
- fs2.accessSync(dir, fs2.constants.R_OK | fs2.constants.W_OK | fs2.constants.X_OK);
2492
- const probe = path3.join(dir, `.parall-workspace-check-${process.pid}-${Date.now()}`);
2493
- const fd = fs2.openSync(probe, "wx", 384);
2494
- fs2.closeSync(fd);
2495
- 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);
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 = path3.relative(parent, child);
2557
- return relative2 !== "" && !relative2.startsWith("..") && !path3.isAbsolute(relative2);
2686
+ const relative2 = path4.relative(parent, child);
2687
+ return relative2 !== "" && !relative2.startsWith("..") && !path4.isAbsolute(relative2);
2558
2688
  }
2559
2689
  function toPolicyPath(value) {
2560
- return path3.resolve(value).split(path3.sep).join("/");
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((resolve4) => {
2706
+ return new Promise((resolve5) => {
2577
2707
  const timer = setTimeout(() => {
2578
2708
  signal.removeEventListener("abort", onAbort);
2579
- resolve4(true);
2709
+ resolve5(true);
2580
2710
  }, ms);
2581
2711
  const onAbort = () => {
2582
2712
  clearTimeout(timer);
2583
- resolve4(false);
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((resolve4) => {
2672
- this.stopResolve = resolve4;
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 = path4.join(root, "agents");
2792
- const flatWorkspace = path4.join(root, "workspace");
2793
- 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))
2794
2928
  return;
2795
2929
  let ownerAgentId;
2796
- const sessionsDir = path4.join(root, "sessions");
2797
- if (fs3.existsSync(sessionsDir)) {
2930
+ const sessionsDir = path5.join(root, "sessions");
2931
+ if (fs4.existsSync(sessionsDir)) {
2798
2932
  try {
2799
- for (const file of fs3.readdirSync(sessionsDir)) {
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 = path4.join(agentsDir, targetId);
2947
+ const targetDir = path5.join(agentsDir, targetId);
2814
2948
  try {
2815
- fs3.mkdirSync(targetDir, { recursive: true });
2949
+ fs4.mkdirSync(targetDir, { recursive: true });
2816
2950
  for (const sub of ["workspace", "sessions", "dispatch-context"]) {
2817
- const src = path4.join(root, sub);
2818
- if (fs3.existsSync(src)) {
2819
- 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));
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
- fs3.mkdirSync(stateDir, { recursive: true });
3160
+ fs4.mkdirSync(stateDir, { recursive: true });
3005
3161
  if (isK8s) {
3006
- fs3.mkdirSync(claudeHome, { recursive: true });
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((resolve4) => {
3112
- const onExit = () => resolve4();
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
- resolve4();
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 = path4.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
3288
+ const sharedCredentials = path5.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
3133
3289
  const agentCredentials = agentClaudeCredentialsFileFor(agentClaudeHome);
3134
- const agentCredentialsDir = path4.dirname(agentCredentials);
3135
- fs3.mkdirSync(path4.dirname(sharedCredentials), { recursive: true });
3136
- 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 });
3137
3293
  try {
3138
- const existing = fs3.lstatSync(agentCredentials);
3294
+ const existing = fs4.lstatSync(agentCredentials);
3139
3295
  if (existing.isSymbolicLink()) {
3140
- const currentTarget = fs3.readlinkSync(agentCredentials);
3141
- if (path4.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
3296
+ const currentTarget = fs4.readlinkSync(agentCredentials);
3297
+ if (path5.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
3142
3298
  return;
3143
3299
  }
3144
- fs3.unlinkSync(agentCredentials);
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
- fs3.unlinkSync(agentCredentials);
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
- fs3.symlinkSync(sharedCredentials, agentCredentials);
3312
+ fs4.symlinkSync(sharedCredentials, agentCredentials);
3157
3313
  }
3158
3314
  };
3159
3315
 
3160
3316
  // ts/daemon/dist/cli.js
3161
- import * as fs4 from "node:fs";
3162
- import * as path5 from "node:path";
3163
- 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";
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(fs4.readFileSync(CONFIG_PATH, "utf-8"));
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
- fs4.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
3177
- fs4.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
3178
- 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);
3179
3335
  }
3180
3336
  function prompt(question) {
3181
3337
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3182
- return new Promise((resolve4) => {
3338
+ return new Promise((resolve5) => {
3183
3339
  rl.question(question, (answer) => {
3184
3340
  rl.close();
3185
- resolve4(answer.trim());
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 path5.join(os2.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
3353
+ return path6.join(os3.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
3198
3354
  }
3199
3355
  function systemdUnitPath() {
3200
- return path5.join(os2.homedir(), ".config", "systemd", "user", "parall-daemon.service");
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 = path5.join(os2.homedir(), "Library", "Logs", "parall-daemon.log");
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 = path5.dirname(plistPath());
3258
- fs4.mkdirSync(dir, { recursive: true });
3259
- fs4.writeFileSync(plistPath(), generatePlist(bin));
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 = path5.dirname(systemdUnitPath());
3265
- fs4.mkdirSync(dir, { recursive: true });
3266
- fs4.writeFileSync(systemdUnitPath(), generateSystemdUnit(bin));
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 = path5.join(os2.homedir(), "Library", "Logs", "parall-daemon.log");
3334
- if (!fs4.existsSync(logPath)) {
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 (fs4.existsSync(plistPath()))
3345
- fs4.unlinkSync(plistPath());
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 (fs4.existsSync(systemdUnitPath()))
3351
- fs4.unlinkSync(systemdUnitPath());
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 {