@akshar5/cohall 0.6.0 → 0.6.2

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/bin/cohall.js CHANGED
@@ -17,7 +17,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
18
  // packages/protocol/src/index.ts
19
19
  import { Effect, Schema } from "effect";
20
- var version = "0.6.0", maxSocketPayloadBytes, bounded = (maxLength) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maxLength)), optionalText = (maxLength) => Schema.String.check(Schema.isMaxLength(maxLength)), boundedArray = (schema, maxLength) => Schema.Array(schema).check(Schema.isMaxLength(maxLength)), count, uuid = (name) => Schema.String.check(Schema.isUUID(4)).pipe(Schema.brand(name)), isoTimestamp = (name) => Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/, {
20
+ var version = "0.6.2", maxSocketPayloadBytes, bounded = (maxLength) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maxLength)), optionalText = (maxLength) => Schema.String.check(Schema.isMaxLength(maxLength)), boundedArray = (schema, maxLength) => Schema.Array(schema).check(Schema.isMaxLength(maxLength)), count, uuid = (name) => Schema.String.check(Schema.isUUID(4)).pipe(Schema.brand(name)), isoTimestamp = (name) => Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/, {
21
21
  expected: "an ISO-8601 UTC timestamp"
22
22
  })).pipe(Schema.brand(name)), BotId, Bot, DeviceId, ThreadId, MessageId, TaskId, AuthSessionId, OperationId, Timestamp, Platform, Provider, DeviceStatus, TaskStatus, OperationStatus, TaskTraceEventKind, MessageRole, ConnectionRole, AuthSession, CreatePairingInput, PairingCredential, ExchangePairingInput, SessionCredential, PairingResult, Capability, Workspace, Device, minimumDeviceOperationVersion = "0.5.0", parsedVersion = (value) => {
23
23
  const match = /^v?(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
@@ -487,13 +487,15 @@ class Hub {
487
487
  hasDevice(deviceId) {
488
488
  return this.#devices.get(deviceId)?.readyState === WebSocket3.OPEN;
489
489
  }
490
- sendToDevice(deviceId, event) {
490
+ sendToDevice(deviceId, event, beforeSend) {
491
491
  const socket = this.#devices.get(deviceId);
492
492
  if (socket?.readyState !== WebSocket3.OPEN || !this.isAuthorized(socket)) {
493
493
  return false;
494
494
  }
495
+ const payload = JSON.stringify(event);
496
+ beforeSend?.();
495
497
  try {
496
- socket.send(JSON.stringify(event));
498
+ socket.send(payload);
497
499
  return true;
498
500
  } catch {
499
501
  return false;
@@ -1067,7 +1069,11 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1067
1069
  }
1068
1070
  return yield* deviceFromRow(row);
1069
1071
  });
1070
- const createDelegation2 = Effect13.fn("RelayStore.createDelegation")(function* (input, targetDeviceId, sourceDeviceId, providerSessionId) {
1072
+ const createDelegation2 = Effect13.fn("RelayStore.createDelegation")(function* (input, targetDeviceId, boundDeviceId, providerSessionId) {
1073
+ const sourceDeviceId = yield* Effect13.try({
1074
+ try: () => boundDeviceId === undefined || db.query("SELECT id FROM devices WHERE id = ? AND forgotten_at IS NULL").get(boundDeviceId) === null ? undefined : boundDeviceId,
1075
+ catch: operationError("RelayStore.createDelegation.source")
1076
+ });
1071
1077
  const timestamp = now();
1072
1078
  const threadId = input.threadId ?? makeThreadId();
1073
1079
  const task = Task.make({
@@ -1207,7 +1213,15 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1207
1213
  const timestamp = now();
1208
1214
  yield* Effect13.try({
1209
1215
  try: () => db.transaction(() => {
1210
- const result = db.query(`UPDATE tasks SET status = 'assigned', updated_at = ?
1216
+ const result = db.query(`UPDATE tasks SET status = 'assigned', updated_at = ?,
1217
+ provider_session_id = CASE
1218
+ WHEN provider = 'grok-bot' THEN NULL
1219
+ WHEN started_at IS NULL THEN COALESCE((
1220
+ SELECT session_id FROM provider_sessions
1221
+ WHERE thread_id = tasks.thread_id AND device_id = tasks.target_device_id
1222
+ AND provider = tasks.provider
1223
+ ), provider_session_id)
1224
+ ELSE provider_session_id END
1211
1225
  WHERE id = ? AND status = 'queued' AND NOT EXISTS (
1212
1226
  SELECT 1 FROM tasks active
1213
1227
  WHERE active.target_device_id = ? AND active.id <> ?
@@ -1278,7 +1292,11 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1278
1292
  if (["completed", "failed", "cancelled"].includes(current.status)) {
1279
1293
  return current;
1280
1294
  }
1281
- if (current.provider === "grok-bot" && current.status !== "queued") {
1295
+ const dispatched = current.provider === "grok-bot" && (yield* Effect13.try({
1296
+ try: () => db.query("SELECT 1 FROM tasks WHERE id = ? AND dispatched_at IS NOT NULL").get(taskId) !== null,
1297
+ catch: operationError("RelayStore.requestCancellation")
1298
+ }));
1299
+ if (current.provider === "grok-bot" && (current.status !== "queued" || current.startedAt !== undefined || dispatched)) {
1282
1300
  return yield* new PersistenceError({
1283
1301
  operation: "RelayStore.requestCancellation",
1284
1302
  message: "Stop this bot in Grok Bot; its gateway cannot cancel an individual Cohall request"
@@ -1358,7 +1376,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1358
1376
  created_at: timestamp,
1359
1377
  expires_at: expiresAt,
1360
1378
  last_seen_at: timestamp,
1361
- bound_device_id: role === "device" ? deviceId ?? null : null,
1379
+ bound_device_id: deviceId ?? null,
1362
1380
  revoked_at: null
1363
1381
  };
1364
1382
  db.query(`INSERT INTO auth_sessions (
@@ -1432,6 +1450,12 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1432
1450
  threadContext: threadContext2,
1433
1451
  pendingTasksFor,
1434
1452
  assignTask,
1453
+ markTaskDispatched: (taskId) => Effect13.try({
1454
+ try: () => {
1455
+ db.query("UPDATE tasks SET dispatched_at = COALESCE(dispatched_at, ?) WHERE id = ? AND status = 'assigned'").run(now(), taskId);
1456
+ },
1457
+ catch: operationError("RelayStore.markTaskDispatched")
1458
+ }),
1435
1459
  rollbackAssignment: (taskId) => transition(taskId, ["assigned"], { status: "queued" }, { kind: "requeued", detail: "Target device was unavailable during dispatch" }),
1436
1460
  acceptTask: (taskId, deviceId) => requireTarget(taskId, deviceId).pipe(Effect13.flatMap(() => transition(taskId, ["assigned"], { status: "running", startedAt: now() }, { kind: "running", detail: "Target device accepted the task and started the provider" }))),
1437
1461
  finishTask: (taskId, deviceId, result, providerSessionId) => terminal(taskId, deviceId, "completed", result, undefined, providerSessionId),
@@ -1487,7 +1511,8 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1487
1511
  source_device_id TEXT REFERENCES devices(id),
1488
1512
  target_device_id TEXT NOT NULL REFERENCES devices(id), parent_task_id TEXT,
1489
1513
  workspace TEXT, provider_session_id TEXT, result TEXT, error TEXT,
1490
- created_at TEXT NOT NULL, updated_at TEXT NOT NULL, started_at TEXT, completed_at TEXT
1514
+ created_at TEXT NOT NULL, updated_at TEXT NOT NULL, started_at TEXT, completed_at TEXT,
1515
+ dispatched_at TEXT
1491
1516
  );
1492
1517
  CREATE INDEX IF NOT EXISTS tasks_thread_created ON tasks(thread_id, created_at);
1493
1518
  CREATE INDEX IF NOT EXISTS tasks_target_status ON tasks(target_device_id, status);
@@ -1535,6 +1560,13 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1535
1560
  if (!taskColumns.some((column) => column.name === "bot_id")) {
1536
1561
  db.exec("ALTER TABLE tasks ADD COLUMN bot_id TEXT");
1537
1562
  }
1563
+ if (!taskColumns.some((column) => column.name === "dispatched_at")) {
1564
+ db.transaction(() => {
1565
+ db.exec("ALTER TABLE tasks ADD COLUMN dispatched_at TEXT");
1566
+ db.exec(`UPDATE tasks SET dispatched_at = COALESCE(started_at, updated_at)
1567
+ WHERE provider = 'grok-bot' AND status NOT IN ('completed', 'failed', 'cancelled')`);
1568
+ })();
1569
+ }
1538
1570
  },
1539
1571
  catch: operationError("RelayStore.migrate")
1540
1572
  }), layer2 = (path, retainedTerminalTasks = 1000) => Layer2.effect(Service2, Effect13.acquireRelease(Effect13.try({
@@ -1555,8 +1587,8 @@ var init_store = __esm(() => {
1555
1587
  var exports_main = {};
1556
1588
  __export(exports_main, {
1557
1589
  runRelay: () => runRelay,
1558
- relayListenOptions: () => relayListenOptions,
1559
- chooseDevice: () => chooseDevice
1590
+ resolveDelegation: () => resolveDelegation,
1591
+ relayListenOptions: () => relayListenOptions
1560
1592
  });
1561
1593
  import { Effect as Effect14, ManagedRuntime, Schema as Schema14 } from "effect";
1562
1594
  import { createHash as createHash3, timingSafeEqual } from "node:crypto";
@@ -1665,10 +1697,40 @@ Content-Length: 0\r
1665
1697
  const needsXcode = /\b(xcode|ios|macos|swiftui|app store)\b/.test(prompt);
1666
1698
  const needsBrowser = /\b(browser|chrome|youtube|twitter|logged[- ]?in|signed[- ]?in|x\.com)\b/.test(prompt);
1667
1699
  return (device.status === "offline" ? 0 : 100) + (input.workspace !== undefined && device.workspaces.some((workspace) => input.workspace === workspace.path || input.workspace?.startsWith(`${workspace.path}/`)) ? 80 : 0) + (needsXcode && device.capabilities.some((capability) => capability.id === "xcode") ? 60 : 0) + (needsBrowser && device.capabilities.some((capability) => capability.id === "browser-session") ? 40 : 0) + (sourceDeviceId === device.id ? -5 : 0);
1668
- }, chooseDevice = (input, sourceDeviceId) => Effect14.gen(function* () {
1700
+ }, resolveDelegation = (input, sourceDeviceId) => Effect14.gen(function* () {
1669
1701
  const store = yield* exports_store.Service;
1702
+ const ancestors = [];
1703
+ const seen = new Set;
1704
+ let nextParentId = input.parentTaskId;
1705
+ while (nextParentId !== undefined && !seen.has(nextParentId)) {
1706
+ const parentId = nextParentId;
1707
+ seen.add(parentId);
1708
+ const parent2 = yield* store.getTask(parentId).pipe(Effect14.catch((cause) => {
1709
+ const missing = cause.message === `Unknown task ${parentId}`;
1710
+ if (missing && ancestors.length > 0)
1711
+ return Effect14.succeed(undefined);
1712
+ return Effect14.fail(new RequestError({ status: missing ? 404 : 500, message: cause.message }));
1713
+ }));
1714
+ if (parent2 === undefined)
1715
+ break;
1716
+ ancestors.push(parent2);
1717
+ nextParentId = parent2.parentTaskId;
1718
+ }
1719
+ const parent = ancestors[0];
1720
+ if (parent !== undefined && input.threadId !== undefined && input.threadId !== parent.threadId) {
1721
+ return yield* new RequestError({
1722
+ status: 400,
1723
+ message: "A child task must use its parent's thread"
1724
+ });
1725
+ }
1726
+ const resolvedInput = parent === undefined ? input : { ...input, threadId: parent.threadId };
1670
1727
  const devices = yield* store.listDevices().pipe(Effect14.mapError((cause) => new RequestError({ status: 500, message: cause.message })));
1671
1728
  const provider = input.provider ?? (input.botId === undefined ? "codex" : "grok-bot");
1729
+ const slot = taskSlot({
1730
+ provider,
1731
+ ...input.botId === undefined ? {} : { botId: input.botId }
1732
+ });
1733
+ const blocked = new Set(ancestors.filter((task) => !isTerminalTask(task) && taskSlot(task) === slot).map((task) => task.targetDeviceId));
1672
1734
  if (input.targetDeviceId !== undefined) {
1673
1735
  const target = devices.find((device) => device.id === input.targetDeviceId);
1674
1736
  if (target === undefined) {
@@ -1689,7 +1751,13 @@ Content-Length: 0\r
1689
1751
  message: `${target.name} does not advertise bot ${input.botId}; refresh the bot list`
1690
1752
  });
1691
1753
  }
1692
- return target.id;
1754
+ if (blocked.has(target.id)) {
1755
+ return yield* new RequestError({
1756
+ status: 409,
1757
+ message: `${target.name} cannot run this task while an unfinished parent occupies the same worker slot; select another device or bot`
1758
+ });
1759
+ }
1760
+ return { input: resolvedInput, targetDeviceId: target.id };
1693
1761
  }
1694
1762
  const candidates = devices.filter((device) => device.providers.includes(provider) && (input.botId === undefined || device.bots?.some((bot) => bot.id === input.botId)));
1695
1763
  if (input.botId !== undefined && candidates.length > 1) {
@@ -1698,14 +1766,14 @@ Content-Length: 0\r
1698
1766
  message: `Bot ${input.botId} is advertised by more than one device; select a device`
1699
1767
  });
1700
1768
  }
1701
- const selected = candidates.sort((left, right) => scoreDevice(right, input, sourceDeviceId) - scoreDevice(left, input, sourceDeviceId))[0];
1769
+ const selected = candidates.filter((device) => !blocked.has(device.id)).sort((left, right) => scoreDevice(right, input, sourceDeviceId) - scoreDevice(left, input, sourceDeviceId))[0];
1702
1770
  if (selected === undefined) {
1703
1771
  return yield* new RequestError({
1704
1772
  status: 409,
1705
- message: input.botId === undefined ? `No Cohall device advertises the ${provider} provider` : `No Cohall device advertises bot ${input.botId}; refresh the bot list`
1773
+ message: candidates.length > 0 ? "No available delegation target avoids an unfinished parent's worker slot; select another device or bot" : input.botId === undefined ? `No Cohall device advertises the ${provider} provider` : `No Cohall device advertises bot ${input.botId}; refresh the bot list`
1706
1774
  });
1707
1775
  }
1708
- return selected.id;
1776
+ return { input: resolvedInput, targetDeviceId: selected.id };
1709
1777
  }), runRelay = async () => {
1710
1778
  const configuration = await Effect14.runPromise(loadEnvironmentConfiguration);
1711
1779
  process.umask(63);
@@ -1741,20 +1809,15 @@ Content-Length: 0\r
1741
1809
  return authenticate(authorization.slice("Bearer ".length), "client");
1742
1810
  };
1743
1811
  const dispatch = async (task) => {
1744
- const assigned = await run2(Effect14.gen(function* () {
1745
- const store = yield* exports_store.Service;
1746
- return yield* store.assignTask(task.id);
1747
- }));
1812
+ const store = await run2(exports_store.Service);
1813
+ const assigned = await Effect14.runPromise(store.assignTask(task.id));
1748
1814
  if (assigned.status !== "assigned") {
1749
1815
  return assigned;
1750
1816
  }
1751
- if (hub.sendToDevice(assigned.targetDeviceId, SocketEvent.make({ _tag: "TaskAssigned", task: assigned }))) {
1817
+ if (hub.sendToDevice(assigned.targetDeviceId, SocketEvent.make({ _tag: "TaskAssigned", task: assigned }), assigned.provider === "grok-bot" ? () => Effect14.runSync(store.markTaskDispatched(assigned.id)) : undefined)) {
1752
1818
  return assigned;
1753
1819
  }
1754
- return run2(Effect14.gen(function* () {
1755
- const store = yield* exports_store.Service;
1756
- return yield* store.rollbackAssignment(task.id);
1757
- }));
1820
+ return Effect14.runPromise(store.rollbackAssignment(task.id));
1758
1821
  };
1759
1822
  const dispatchOperation = async (operation) => {
1760
1823
  const assigned = await run2(Effect14.gen(function* () {
@@ -2009,8 +2072,7 @@ Content-Length: 0\r
2009
2072
  return json(forgotten);
2010
2073
  }
2011
2074
  if (url.pathname === "/api/tasks" && request.method === "POST") {
2012
- const input = yield* body(request, decodeCreateTaskInput);
2013
- const target = yield* chooseDevice(input, sourceDeviceId);
2075
+ const { input, targetDeviceId: target } = yield* resolveDelegation(yield* body(request, decodeCreateTaskInput), sourceDeviceId);
2014
2076
  const providerSessionId = input.threadId === undefined || input.provider === "grok-bot" ? undefined : yield* store.sessionFor(input.threadId, target, input.provider ?? "codex");
2015
2077
  const task2 = yield* store.createDelegation(input, target, sourceDeviceId, providerSessionId);
2016
2078
  return json(yield* Effect14.tryPromise(() => dispatch(task2)), 201);
@@ -2051,6 +2113,7 @@ Content-Length: 0\r
2051
2113
  });
2052
2114
  websocketServer.on("connection", (rawSocket) => {
2053
2115
  const socket = rawSocket;
2116
+ socket.on("error", () => socket.terminate());
2054
2117
  socket.data = {
2055
2118
  processing: Promise.resolve(),
2056
2119
  stageDeadline: undefined,
@@ -2127,7 +2190,11 @@ Content-Length: 0\r
2127
2190
  }).then((response) => sendResponse(outgoing, response)).catch((cause) => sendResponse(outgoing, json({ error: cause instanceof Error ? cause.message : "Invalid request" }, cause instanceof Error && cause.message.includes("256 KiB") ? 413 : 400)));
2128
2191
  });
2129
2192
  server.on("upgrade", (request, socket, head) => {
2130
- const url = new URL(request.url ?? "/", "http://cohall.local");
2193
+ const url = URL.parse(request.url ?? "/", "http://cohall.local");
2194
+ if (url === null) {
2195
+ rejectUpgrade(socket, 400, "Bad Request");
2196
+ return;
2197
+ }
2131
2198
  if (url.pathname !== "/ws/device") {
2132
2199
  rejectUpgrade(socket, 404, "Not Found");
2133
2200
  return;
@@ -2343,11 +2410,12 @@ init_src();
2343
2410
  // packages/providers/src/index.ts
2344
2411
  init_src();
2345
2412
  import { Effect as Effect3, Schema as Schema3 } from "effect";
2413
+ import { execa } from "execa";
2346
2414
  import { accessSync, constants } from "node:fs";
2347
2415
  import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
2348
2416
  import { homedir, platform, tmpdir } from "node:os";
2349
2417
  import { delimiter, extname, isAbsolute, join } from "node:path";
2350
- import { execFile, spawn } from "node:child_process";
2418
+ import { execFile } from "node:child_process";
2351
2419
 
2352
2420
  class ProviderUnavailableError extends Schema3.TaggedErrorClass()("CohallProvider.Unavailable", { provider: Provider, message: Schema3.String }) {
2353
2421
  }
@@ -2364,7 +2432,10 @@ var executables = {
2364
2432
  opencode: "opencode"
2365
2433
  };
2366
2434
  var providerEnvironment = () => ({
2367
- ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("COHALL_") || name === "COHALL_CONFIG")),
2435
+ ...Object.fromEntries(Object.entries(process.env).filter(([name]) => {
2436
+ const key = platform() === "win32" ? name.toUpperCase() : name;
2437
+ return !key.startsWith("COHALL_") || key === "COHALL_CONFIG";
2438
+ })),
2368
2439
  PATH: executableDirectories().join(delimiter)
2369
2440
  });
2370
2441
  var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? Object.fromEntries(Object.entries(value)) : undefined;
@@ -2708,8 +2779,8 @@ var terminateProcessTree = async (pid) => {
2708
2779
  process.kill(-pid, "SIGKILL");
2709
2780
  await waitForProcessGroup(pid, 1000);
2710
2781
  };
2711
- var run = (options) => Effect3.tryPromise({
2712
- try: async (signal) => {
2782
+ var run = (options) => Effect3.callback((resume, signal) => {
2783
+ const running = (async () => {
2713
2784
  const executable = findExecutable(executables[options.provider]);
2714
2785
  if (executable === undefined) {
2715
2786
  throw new ProviderUnavailableError({
@@ -2720,8 +2791,9 @@ var run = (options) => Effect3.tryPromise({
2720
2791
  const prepared = await prepareCommand(options);
2721
2792
  try {
2722
2793
  await options.beforeSpawn?.();
2794
+ signal.throwIfAborted();
2723
2795
  const [, ...arguments_] = prepared.command;
2724
- const child = spawn(executable, arguments_, {
2796
+ const child = execa(executable, arguments_, {
2725
2797
  cwd: options.cwd,
2726
2798
  env: {
2727
2799
  ...providerEnvironment(),
@@ -2729,12 +2801,22 @@ var run = (options) => Effect3.tryPromise({
2729
2801
  COHALL_THREAD_ID: options.threadId,
2730
2802
  ...options.taskId === undefined ? {} : { COHALL_TASK_ID: options.taskId }
2731
2803
  },
2804
+ extendEnv: false,
2732
2805
  detached: platform() !== "win32",
2733
- stdio: ["pipe", "pipe", "pipe"]
2806
+ stdin: "pipe",
2807
+ stdout: "pipe",
2808
+ stderr: "pipe",
2809
+ buffer: false,
2810
+ reject: false,
2811
+ cleanup: false
2734
2812
  });
2735
- const exited = new Promise((resolve, reject) => {
2736
- child.once("error", reject);
2737
- child.once("exit", (code) => resolve(code ?? 1));
2813
+ let finished = false;
2814
+ const exited = child.then(({ exitCode, failed, originalMessage, shortMessage }) => {
2815
+ finished = true;
2816
+ if (failed && exitCode === undefined) {
2817
+ throw new Error(originalMessage ?? shortMessage ?? "Provider failed to start");
2818
+ }
2819
+ return exitCode ?? 1;
2738
2820
  });
2739
2821
  let termination;
2740
2822
  const terminate = () => {
@@ -2749,12 +2831,16 @@ var run = (options) => Effect3.tryPromise({
2749
2831
  if (signal.aborted) {
2750
2832
  terminate();
2751
2833
  }
2752
- child.stdin.end(prepared.input);
2753
2834
  try {
2835
+ const written = new Promise((resolve, reject) => {
2836
+ child.stdin.once("error", reject);
2837
+ child.stdin.end(prepared.input, (cause) => cause === undefined || cause === null ? resolve() : reject(cause));
2838
+ });
2754
2839
  const [result, stderr, exitCode] = await Promise.all([
2755
2840
  parse(options, child.stdout, terminate),
2756
2841
  captureText(child.stderr, 64 * 1024),
2757
- exited
2842
+ exited,
2843
+ written
2758
2844
  ]);
2759
2845
  if (exitCode !== 0) {
2760
2846
  throw new ProviderRunError({
@@ -2778,7 +2864,7 @@ var run = (options) => Effect3.tryPromise({
2778
2864
  return result;
2779
2865
  } finally {
2780
2866
  signal.removeEventListener("abort", terminate);
2781
- if (child.exitCode === null) {
2867
+ if (!finished) {
2782
2868
  terminate();
2783
2869
  await exited.catch(() => {
2784
2870
  return;
@@ -2789,21 +2875,21 @@ var run = (options) => Effect3.tryPromise({
2789
2875
  } finally {
2790
2876
  await prepared.cleanup();
2791
2877
  }
2792
- },
2793
- catch: (cause) => {
2794
- if (cause instanceof ProviderUnavailableError || cause instanceof ProviderRunError) {
2795
- return cause;
2796
- }
2797
- return new ProviderRunError({
2798
- provider: options.provider,
2799
- message: (cause instanceof Error ? cause.message : String(cause)).slice(0, 16384)
2800
- });
2801
- }
2878
+ })();
2879
+ running.then((result) => resume(Effect3.succeed(result)), (cause) => resume(Effect3.fail(cause instanceof ProviderUnavailableError || cause instanceof ProviderRunError ? cause : new ProviderRunError({
2880
+ provider: options.provider,
2881
+ message: (cause instanceof Error ? cause.message : String(cause)).slice(0, 16384)
2882
+ }))));
2883
+ return Effect3.promise(() => running.then(() => {
2884
+ return;
2885
+ }, () => {
2886
+ return;
2887
+ }));
2802
2888
  });
2803
2889
 
2804
2890
  // apps/device/src/cli.ts
2805
2891
  import { Effect as Effect9, Schema as Schema10 } from "effect";
2806
- import { access as access4, mkdir as mkdir6, readFile as readFile4, stat as stat3, writeFile as writeFile6 } from "node:fs/promises";
2892
+ import { access as access4, mkdir as mkdir6, readFile as readFile4, stat as stat4, writeFile as writeFile6 } from "node:fs/promises";
2807
2893
  import { homedir as homedir4 } from "node:os";
2808
2894
  import { dirname as dirname6, join as join7, resolve as resolve3 } from "node:path";
2809
2895
 
@@ -3013,7 +3099,7 @@ Return the diagnostic failure to the user rather than claiming remote work ran.
3013
3099
  // apps/device/src/config.ts
3014
3100
  init_src();
3015
3101
  import { Effect as Effect4, Schema as Schema4 } from "effect";
3016
- import { access, chmod as chmod2, mkdir, readFile, realpath, rename, writeFile as writeFile2 } from "node:fs/promises";
3102
+ import { access, chmod as chmod2, mkdir, readFile, realpath, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
3017
3103
  import { homedir as homedir2, hostname, platform as platform2 } from "node:os";
3018
3104
  import { dirname, join as join2, resolve } from "node:path";
3019
3105
  var WorkspaceList = Schema4.Array(Schema4.NonEmptyString).check(Schema4.isMaxLength(64));
@@ -3086,10 +3172,16 @@ var normalizeRelayUrl = (input) => {
3086
3172
  var parseWorkspaces = (workspaceList, workspaceJson) => Effect4.tryPromise({
3087
3173
  try: async () => {
3088
3174
  const values = workspaceJson === undefined ? workspaceList.split(",").map((workspace) => workspace.trim()).filter((workspace) => workspace.length > 0) : Schema4.decodeUnknownSync(WorkspaceList)(JSON.parse(workspaceJson));
3089
- const canonical = await Promise.all(values.map((workspace) => realpath(resolve(workspace))));
3175
+ const canonical = await Promise.all(values.map(async (workspace) => {
3176
+ const path = await realpath(resolve(workspace));
3177
+ if (!(await stat(path)).isDirectory()) {
3178
+ throw new Error(`Not a directory: ${workspace}`);
3179
+ }
3180
+ return path;
3181
+ }));
3090
3182
  return [...new Set(canonical)];
3091
3183
  },
3092
- catch: (cause) => new Error(`Workspace roots must be existing paths; JSON input must be an array of paths: ${cause instanceof Error ? cause.message : String(cause)}`)
3184
+ catch: (cause) => new Error(`Workspace roots must be existing directories; JSON input must be an array of paths: ${cause instanceof Error ? cause.message : String(cause)}`)
3093
3185
  });
3094
3186
  var parseProviders = (input) => Schema4.decodeUnknownSync(ProviderList)([
3095
3187
  ...new Set(input.split(",").map((provider) => provider.trim()).filter(Boolean))
@@ -3785,15 +3877,10 @@ var discoverGrokBots = async (path) => {
3785
3877
  ...description === undefined ? {} : { description: description.slice(0, 512) }
3786
3878
  }));
3787
3879
  };
3788
- var poll = async (pass, signal) => {
3789
- const result = await Effect6.runPromise(Effect6.tryPromise({ try: pass, catch: (cause) => cause }).pipe(Effect6.repeat({
3790
- schedule: Schedule2.spaced("1 second"),
3791
- until: (value) => value !== undefined
3792
- })), { signal });
3793
- if (result === undefined)
3794
- throw new Error("Grok Bot polling ended before a result");
3795
- return result;
3796
- };
3880
+ var poll = (pass, intervalMs = 1000) => Effect6.tryPromise({ try: pass, catch: (cause) => cause }).pipe(Effect6.repeat({
3881
+ schedule: Schedule2.spaced(intervalMs),
3882
+ until: (value) => value !== undefined
3883
+ }));
3797
3884
  var timeoutMessage = "Timed out waiting for the bot to reply through Cohall; the bot may still be running.";
3798
3885
  var requireTime = (deadline) => {
3799
3886
  if (Date.now() >= deadline)
@@ -3827,22 +3914,41 @@ var runGrokBot = async (path, task, signal) => {
3827
3914
  return finishReply(existing);
3828
3915
  const { deadline, command, dispatched } = await prepareBotReply(task);
3829
3916
  requireTime(deadline);
3830
- const waitForReply = () => poll(async () => {
3831
- const receipt = await readBotReply(task.id);
3832
- if (receipt !== undefined)
3833
- return finishReply(receipt);
3834
- requireTime(deadline);
3835
- return;
3836
- }, signal);
3837
- if (dispatched)
3838
- return waitForReply();
3839
- if (path === undefined)
3840
- throw new Error("Configure this device's Grok Bot gateway discovery file first");
3841
3917
  const agentId = task.botId;
3842
3918
  if (agentId === undefined)
3843
3919
  throw new Error("Select a Grok Bot for this task");
3920
+ const lookup = async (lookupSignal = signal) => {
3921
+ if (path === undefined)
3922
+ throw new Error("Configure this device's Grok Bot gateway discovery file first");
3923
+ return rpc(await readConnection(path), "promptAcceptanceStatus", { accountSlot: "host", agentId, clientNonce: task.id }, Acceptance, lookupSignal);
3924
+ };
3925
+ const waitForReply = (reconcile = false) => {
3926
+ const receipt = poll(async () => {
3927
+ const receipt2 = await readBotReply(task.id);
3928
+ if (receipt2 !== undefined)
3929
+ return finishReply(receipt2);
3930
+ requireTime(deadline);
3931
+ return;
3932
+ });
3933
+ if (!reconcile || path === undefined)
3934
+ return Effect6.runPromise(receipt, { signal });
3935
+ const acceptance2 = poll(async (lookupSignal) => {
3936
+ const current = await lookup(lookupSignal).catch(() => {
3937
+ return;
3938
+ });
3939
+ if (current?.outcome !== "found")
3940
+ return;
3941
+ if (current.record.status === "rejected")
3942
+ throw new Error("Grok Bot rejected this task; check its conversation");
3943
+ return current.record.status === "accepted" ? true : undefined;
3944
+ }, 5000).pipe(Effect6.andThen(Effect6.never));
3945
+ return Effect6.runPromise(Effect6.raceFirst(receipt, acceptance2), { signal });
3946
+ };
3947
+ if (dispatched)
3948
+ return waitForReply(true);
3949
+ if (path === undefined)
3950
+ throw new Error("Configure this device's Grok Bot gateway discovery file first");
3844
3951
  const connection = await readConnection(path);
3845
- const lookup = () => rpc(connection, "promptAcceptanceStatus", { accountSlot: "host", agentId, clientNonce: task.id }, Acceptance, signal);
3846
3952
  let acceptance = await lookup();
3847
3953
  if (acceptance.outcome === "unknown-durability")
3848
3954
  throw new Error("Grok Bot cannot confirm whether this task was accepted; check its conversation before retrying");
@@ -3860,7 +3966,7 @@ var runGrokBot = async (path, task, signal) => {
3860
3966
  if (acceptance.outcome === "not-found") {
3861
3967
  requireTime(deadline);
3862
3968
  if (!await claimBotDispatch(task.id)) {
3863
- return waitForReply();
3969
+ return waitForReply(true);
3864
3970
  }
3865
3971
  try {
3866
3972
  await rpc(connection, "sendPrompt", {
@@ -3875,20 +3981,16 @@ var runGrokBot = async (path, task, signal) => {
3875
3981
  const receipt2 = await readBotReply(task.id);
3876
3982
  if (receipt2 !== undefined)
3877
3983
  return finishReply(receipt2);
3878
- const reconciled = await lookup().catch(() => {
3879
- return;
3880
- });
3881
- if (reconciled?.outcome === "found" && reconciled.record.status === "rejected")
3882
- throw new Error("Grok Bot rejected this task; check its conversation");
3883
- return waitForReply();
3984
+ return waitForReply(true);
3884
3985
  }
3986
+ return waitForReply();
3885
3987
  }
3886
3988
  }
3887
3989
  if (acceptance.outcome === "unknown-durability")
3888
3990
  throw new Error("Grok Bot cannot confirm task acceptance; the prompt was not resent");
3889
3991
  if (acceptance.outcome === "found" && acceptance.record.status === "rejected")
3890
3992
  throw new Error("Grok Bot rejected this task; check its conversation");
3891
- return waitForReply();
3993
+ return waitForReply(acceptance.outcome === "found" && acceptance.record.status === "pending");
3892
3994
  };
3893
3995
 
3894
3996
  // apps/device/src/setup.ts
@@ -4003,7 +4105,7 @@ import {
4003
4105
  realpath as realpath4,
4004
4106
  rename as rename3,
4005
4107
  rm as rm3,
4006
- stat as stat2,
4108
+ stat as stat3,
4007
4109
  writeFile as writeFile5
4008
4110
  } from "node:fs/promises";
4009
4111
  import { basename, dirname as dirname5, join as join6, resolve as resolve2 } from "node:path";
@@ -4011,14 +4113,14 @@ import { backup as sqliteBackup, DatabaseSync } from "node:sqlite";
4011
4113
  import { WebSocket } from "ws";
4012
4114
 
4013
4115
  // apps/device/src/service.ts
4014
- import { execFile as execFile3 } from "node:child_process";
4116
+ import { execFile as execFile2 } from "node:child_process";
4015
4117
  import { chmod as chmod5, mkdir as mkdir4, realpath as realpath3, writeFile as writeFile4 } from "node:fs/promises";
4016
4118
  import { homedir as homedir3 } from "node:os";
4017
4119
  import { dirname as dirname4, join as join5 } from "node:path";
4018
4120
 
4019
4121
  // apps/device/src/upgrade.ts
4020
4122
  import { Schema as Schema8 } from "effect";
4021
- import { execFile as execFile2 } from "node:child_process";
4123
+ import { execa as execa2 } from "execa";
4022
4124
  import { constants as constants2 } from "node:fs";
4023
4125
  import {
4024
4126
  access as access2,
@@ -4028,7 +4130,7 @@ import {
4028
4130
  realpath as realpath2,
4029
4131
  rename as rename2,
4030
4132
  rm as rm2,
4031
- stat,
4133
+ stat as stat2,
4032
4134
  writeFile as writeFile3
4033
4135
  } from "node:fs/promises";
4034
4136
  import { delimiter as delimiter2, dirname as dirname3, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative } from "node:path";
@@ -4116,33 +4218,20 @@ var linuxSystemExecutableRoots = [
4116
4218
  ];
4117
4219
  var macosHomebrewRoots = ["/opt/homebrew", "/usr/local"];
4118
4220
  var macosAdminGroup = 80;
4119
- var isTrustedSystemExecutablePath = (platform4, canonical) => platform4 === "linux" && linuxSystemExecutableRoots.some((root) => containsPath(root, canonical));
4221
+ var isTrustedSystemPath = (platform4, path) => platform4 === "linux" && (path === "/home" || linuxSystemExecutableRoots.some((root) => containsPath(root, path) || containsPath(path, root)));
4120
4222
  var isTrustedGroupWritablePath = (options) => {
4121
4223
  const writableRoot = options.writableRoot;
4122
4224
  return options.platform === "darwin" && writableRoot !== undefined && options.uid !== undefined && options.ownerUid === options.uid && options.ownerGid === macosAdminGroup && macosHomebrewRoots.some((root) => containsPath(root, writableRoot)) && containsPath(writableRoot, options.canonical) && containsPath(writableRoot, options.path);
4123
4225
  };
4124
- var trustedExecutable = async (command, options = {}) => {
4125
- const candidates = isAbsolute2(command) ? [command] : (process.env.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((directory) => executableNames(command).map((name) => join4(directory, name)));
4126
- let selected;
4127
- for (const path of candidates) {
4128
- const available2 = await access2(path, operatingSystem() === "win32" ? constants2.F_OK : constants2.X_OK).then(() => true).catch(() => false);
4129
- if (available2) {
4130
- selected = path;
4131
- break;
4132
- }
4133
- }
4134
- if (selected === undefined) {
4135
- throw new Error(`Could not find ${command} on PATH`);
4136
- }
4137
- const canonical = await realpath2(selected);
4226
+ var validateExecutable = async (candidate, installationRoot) => {
4227
+ const canonical = await realpath2(candidate);
4138
4228
  if (operatingSystem() === "win32") {
4139
4229
  return canonical;
4140
4230
  }
4141
4231
  const uid = process.getuid?.();
4142
- const writableRoot = options.writableRoot === undefined ? undefined : await realpath2(options.writableRoot);
4143
- const systemExecutable = isTrustedSystemExecutablePath(operatingSystem(), canonical);
4232
+ const writableRoot = installationRoot === undefined ? undefined : await realpath2(installationRoot);
4144
4233
  for (let path = canonical;; path = dirname3(path)) {
4145
- const metadata = await stat(path);
4234
+ const metadata = await stat2(path);
4146
4235
  const trustedGroupWritablePath = isTrustedGroupWritablePath({
4147
4236
  platform: operatingSystem(),
4148
4237
  canonical,
@@ -4155,7 +4244,7 @@ var trustedExecutable = async (command, options = {}) => {
4155
4244
  if ((metadata.mode & 2) !== 0 || (metadata.mode & 16) !== 0 && !trustedGroupWritablePath) {
4156
4245
  throw new Error(`Refusing executable beneath group- or world-writable path ${path}`);
4157
4246
  }
4158
- if (uid !== undefined && metadata.uid !== 0 && metadata.uid !== uid && !systemExecutable) {
4247
+ if (uid !== undefined && metadata.uid !== 0 && metadata.uid !== uid && !isTrustedSystemPath(operatingSystem(), path)) {
4159
4248
  throw new Error(`Refusing executable owned by another user at ${path}`);
4160
4249
  }
4161
4250
  const parent = dirname3(path);
@@ -4165,6 +4254,21 @@ var trustedExecutable = async (command, options = {}) => {
4165
4254
  }
4166
4255
  return canonical;
4167
4256
  };
4257
+ var trustedExecutable = async (command, options = {}) => {
4258
+ const candidates = isAbsolute2(command) ? [command] : (process.env.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((directory) => executableNames(command).map((name) => join4(directory, name)));
4259
+ let failure;
4260
+ for (const path of candidates) {
4261
+ const available2 = await access2(path, operatingSystem() === "win32" ? constants2.F_OK : constants2.X_OK).then(() => true).catch(() => false);
4262
+ if (!available2)
4263
+ continue;
4264
+ try {
4265
+ return await validateExecutable(path, options.writableRoot);
4266
+ } catch (cause) {
4267
+ failure ??= cause;
4268
+ }
4269
+ }
4270
+ throw failure ?? new Error(`Could not find ${command} on PATH`);
4271
+ };
4168
4272
  var trustedServices = async (services, resolveExecutable) => {
4169
4273
  const resolved = new Map;
4170
4274
  const invocation = async (value) => {
@@ -4273,16 +4377,23 @@ var serviceCandidates = (runtimePlatform, uid) => {
4273
4377
  return [];
4274
4378
  };
4275
4379
  var defaultRunner = {
4276
- run: (command, arguments_, timeoutMs = 300000) => new Promise((resolve2) => {
4277
- execFile2(command, [...arguments_], { encoding: "utf8", maxBuffer: 1024 * 1024, timeout: timeoutMs, windowsHide: true }, (error, stdout, stderr) => {
4278
- resolve2({
4279
- exitCode: typeof error?.code === "number" ? error.code : error === null ? 0 : 1,
4280
- stdout,
4281
- stderr,
4282
- ...error === null ? {} : { error: error.message }
4283
- });
4380
+ run: async (command, arguments_, timeoutMs = 300000) => {
4381
+ const result = await execa2(command, arguments_, {
4382
+ encoding: "utf8",
4383
+ maxBuffer: 1024 * 1024,
4384
+ timeout: timeoutMs,
4385
+ windowsHide: true,
4386
+ stdin: "ignore",
4387
+ reject: false,
4388
+ stripFinalNewline: false
4284
4389
  });
4285
- })
4390
+ return {
4391
+ exitCode: result.failed ? result.exitCode || 1 : 0,
4392
+ stdout: result.stdout,
4393
+ stderr: result.stderr,
4394
+ ...result.shortMessage === undefined ? {} : { error: result.shortMessage.slice(0, 16384) }
4395
+ };
4396
+ }
4286
4397
  };
4287
4398
  var checked = async (runner, invocation, timeoutMs) => {
4288
4399
  const result = await runner.run(invocation.command, invocation.arguments, timeoutMs);
@@ -4382,6 +4493,7 @@ var restartServices = async (runner, services, statePath, state, preserveDeviceR
4382
4493
  };
4383
4494
  var reportedRestartServices = (receipt) => receipt.restartingService === undefined || receipt.restartedServices.includes(receipt.restartingService) ? receipt.restartedServices : [...receipt.restartedServices, receipt.restartingService];
4384
4495
  var upgrade = async (options) => {
4496
+ const target = normalizeUpgradeTarget(options.target);
4385
4497
  const runner = options.runner ?? defaultRunner;
4386
4498
  const resolveExecutable = options.resolveExecutable ?? trustedExecutable;
4387
4499
  const runtimePlatform = options.platform ?? process.platform;
@@ -4389,7 +4501,7 @@ var upgrade = async (options) => {
4389
4501
  const candidates = await trustedServices(serviceCandidates(runtimePlatform, uid), resolveExecutable);
4390
4502
  const statePath = options.statePath ?? restartReceiptPath();
4391
4503
  const previous = await readReceipt(statePath);
4392
- if (previous?.version === options.currentVersion) {
4504
+ if (previous?.version === options.currentVersion && (target === "latest" || target === previous.version)) {
4393
4505
  const byId = new Map(candidates.map((service) => [service.id, service]));
4394
4506
  const pending = previous.pendingServices.flatMap((id) => {
4395
4507
  const service = byId.get(id);
@@ -4442,15 +4554,12 @@ var upgrade = async (options) => {
4442
4554
  dry_run: false
4443
4555
  };
4444
4556
  }
4445
- await rm2(statePath, { force: true });
4446
- const target = normalizeUpgradeTarget(options.target);
4447
4557
  const entrypoint = options.entrypoint ?? process.argv[1];
4448
4558
  if (entrypoint === undefined) {
4449
4559
  throw new Error("Could not resolve the Cohall executable path");
4450
4560
  }
4451
4561
  const canonicalEntrypoint = await realpath2(entrypoint);
4452
4562
  const installation = packageInstallation(canonicalEntrypoint, entrypoint);
4453
- const install = packageInstallCommand(installation, target);
4454
4563
  const services = await activeServices(runner, candidates);
4455
4564
  await assertServiceInstallations(runner, services, canonicalEntrypoint, entrypoint);
4456
4565
  if (options.dryRun) {
@@ -4466,14 +4575,21 @@ var upgrade = async (options) => {
4466
4575
  dry_run: true
4467
4576
  };
4468
4577
  }
4469
- const installExecutable = options.resolveExecutable === undefined ? await trustedExecutable(install.command, installation.prefix === undefined ? {} : { writableRoot: installation.prefix }) : await resolveExecutable(install.command);
4470
- await checked(runner, { ...install, command: installExecutable });
4471
- const nextVersion = await installedVersion(entrypoint);
4578
+ let nextVersion = await installedVersion(entrypoint).catch(() => {
4579
+ return;
4580
+ });
4581
+ if (nextVersion === undefined || target !== nextVersion) {
4582
+ const install = packageInstallCommand(installation, target);
4583
+ const installExecutable = options.resolveExecutable === undefined ? await trustedExecutable(install.command, installation.prefix === undefined ? {} : { writableRoot: installation.prefix }) : await resolveExecutable(install.command);
4584
+ await checked(runner, { ...install, command: installExecutable });
4585
+ nextVersion = await installedVersion(entrypoint);
4586
+ }
4472
4587
  if (target !== "latest" && nextVersion !== target) {
4473
4588
  throw new Error(`Installed Cohall ${nextVersion}, expected ${target}`);
4474
4589
  }
4475
4590
  const upgraded = nextVersion !== options.currentVersion;
4476
4591
  if (!options.restart || services.length === 0) {
4592
+ await rm2(statePath, { force: true });
4477
4593
  return {
4478
4594
  upgraded,
4479
4595
  from_version: options.currentVersion,
@@ -4513,8 +4629,20 @@ var upgrade = async (options) => {
4513
4629
 
4514
4630
  // apps/device/src/service.ts
4515
4631
  var xml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
4516
- var systemdArgument = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
4632
+ var systemdArgument = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%").replaceAll(`
4633
+ `, "\\n").replaceAll("\r", "\\r")}"`;
4517
4634
  var deviceServicePlan = (options) => {
4635
+ const servicePath = [
4636
+ dirname4(options.nodeExecutable),
4637
+ join5(options.home, ".local", "bin"),
4638
+ join5(options.home, ".npm-global", "bin"),
4639
+ join5(options.home, ".bun", "bin"),
4640
+ options.platform === "darwin" ? join5(options.home, "Library", "pnpm") : join5(options.home, ".local", "share", "pnpm"),
4641
+ ...options.platform === "darwin" ? ["/opt/homebrew/bin"] : [],
4642
+ "/usr/local/bin",
4643
+ "/usr/bin",
4644
+ "/bin"
4645
+ ].join(":");
4518
4646
  if (options.platform === "linux") {
4519
4647
  const path = join5(options.home, ".config", "systemd", "user", "cohall-device.service");
4520
4648
  return {
@@ -4528,7 +4656,8 @@ Wants=network-online.target
4528
4656
 
4529
4657
  [Service]
4530
4658
  Type=simple
4531
- Environment=PATH=%h/.local/bin:%h/.npm-global/bin:%h/.bun/bin:%h/.local/share/pnpm:/usr/local/bin:/usr/bin:/bin
4659
+ Environment=${systemdArgument(`PATH=${servicePath}`)}
4660
+ Environment=${systemdArgument(`COHALL_CONFIG=${options.configPath}`)}
4532
4661
  ExecStart=${systemdArgument(options.entrypoint)} device
4533
4662
  Restart=always
4534
4663
  RestartSec=3
@@ -4542,7 +4671,8 @@ WantedBy=default.target
4542
4671
  },
4543
4672
  commands: [
4544
4673
  { command: "systemctl", arguments: ["--user", "daemon-reload"] },
4545
- { command: "systemctl", arguments: ["--user", "enable", "--now", "cohall-device.service"] }
4674
+ { command: "systemctl", arguments: ["--user", "enable", "cohall-device.service"] },
4675
+ { command: "systemctl", arguments: ["--user", "restart", "cohall-device.service"] }
4546
4676
  ],
4547
4677
  note: "The device starts after login. Run loginctl enable-linger $USER if it must run before login."
4548
4678
  };
@@ -4569,7 +4699,9 @@ WantedBy=default.target
4569
4699
  <key>EnvironmentVariables</key>
4570
4700
  <dict>
4571
4701
  <key>PATH</key>
4572
- <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
4702
+ <string>${xml(servicePath)}</string>
4703
+ <key>COHALL_CONFIG</key>
4704
+ <string>${xml(options.configPath)}</string>
4573
4705
  </dict>
4574
4706
  <key>RunAtLoad</key>
4575
4707
  <true/>
@@ -4601,7 +4733,7 @@ var defaultRunner2 = {
4601
4733
  run: async (command, arguments_, timeoutMs = 60000) => {
4602
4734
  const executable = await trustedExecutable(command);
4603
4735
  return new Promise((resolve2) => {
4604
- execFile3(executable, [...arguments_], { encoding: "utf8", maxBuffer: 1024 * 1024, timeout: timeoutMs, windowsHide: true }, (error, stdout, stderr) => resolve2({
4736
+ execFile2(executable, [...arguments_], { encoding: "utf8", maxBuffer: 1024 * 1024, timeout: timeoutMs, windowsHide: true }, (error, stdout, stderr) => resolve2({
4605
4737
  exitCode: typeof error?.code === "number" ? error.code : error === null ? 0 : 1,
4606
4738
  stdout,
4607
4739
  stderr,
@@ -4629,7 +4761,13 @@ var installDeviceService = async (options = {}) => {
4629
4761
  "-ExecutionPolicy",
4630
4762
  "Bypass",
4631
4763
  "-File",
4632
- script
4764
+ script,
4765
+ "-NodeExecutable",
4766
+ process.execPath,
4767
+ "-Entrypoint",
4768
+ entrypoint,
4769
+ "-ConfigurationPath",
4770
+ configurationPath()
4633
4771
  ]);
4634
4772
  return { installed: "scheduled-task:Cohall Device" };
4635
4773
  }
@@ -4638,6 +4776,8 @@ var installDeviceService = async (options = {}) => {
4638
4776
  platform: platform4,
4639
4777
  entrypoint,
4640
4778
  home: options.home ?? homedir3(),
4779
+ nodeExecutable: process.execPath,
4780
+ configPath: configurationPath(),
4641
4781
  ...uid === undefined ? {} : { uid }
4642
4782
  });
4643
4783
  await mkdir4(dirname4(plan.file.path), { recursive: true, mode: 448 });
@@ -4721,7 +4861,7 @@ var verifyBackupDestinationParent = async (path) => {
4721
4861
  return;
4722
4862
  }
4723
4863
  for (let current = await realpath4(path);; current = dirname5(current)) {
4724
- const metadata = await stat2(current);
4864
+ const metadata = await stat3(current);
4725
4865
  const writableByOthers = (metadata.mode & 18) !== 0;
4726
4866
  const sticky = (metadata.mode & 512) !== 0;
4727
4867
  if (writableByOthers && !sticky) {
@@ -4742,7 +4882,7 @@ var ownerToken = async (dataDirectory) => {
4742
4882
  if (!await exists(path)) {
4743
4883
  throw new Error(`Relay owner token was not found at ${path}`);
4744
4884
  }
4745
- if ((await stat2(path)).size > 512) {
4885
+ if ((await stat3(path)).size > 512) {
4746
4886
  throw new Error("Relay owner token file exceeds 512 bytes");
4747
4887
  }
4748
4888
  return (await readFile3(path, "utf8")).trim();
@@ -5148,7 +5288,7 @@ var readInput = async (arguments_, name) => {
5148
5288
  if (!await access4(path).then(() => true).catch(() => false)) {
5149
5289
  throw new Error(`File does not exist: ${path}`);
5150
5290
  }
5151
- if ((await stat3(path)).size > byteLimit) {
5291
+ if ((await stat4(path)).size > byteLimit) {
5152
5292
  throw new Error(`${name} file exceeds ${byteLimit / 1024} KiB`);
5153
5293
  }
5154
5294
  }
@@ -5183,7 +5323,7 @@ var pairingToken = async (arguments_) => {
5183
5323
  if (path !== undefined && !await access4(path).then(() => true).catch(() => false)) {
5184
5324
  throw new Error(`Pairing token file does not exist: ${path}`);
5185
5325
  }
5186
- if (path !== undefined && (await stat3(path)).size > 512) {
5326
+ if (path !== undefined && (await stat4(path)).size > 512) {
5187
5327
  throw new Error("Pairing token file exceeds 512 bytes");
5188
5328
  }
5189
5329
  const token = (path === undefined ? await readStdin(512, "Pairing token") : await readFile4(path, "utf8")).trim();
@@ -5681,6 +5821,7 @@ var runCli = async (command, raw) => {
5681
5821
  "timeout",
5682
5822
  "workspace"
5683
5823
  ]);
5824
+ const waitSeconds = timeout(arguments_);
5684
5825
  const positionalTarget = command === "send" && arguments_.positionals[0]?.startsWith("@") ? arguments_.positionals[0] : undefined;
5685
5826
  const positionals = positionalTarget === undefined ? arguments_.positionals : arguments_.positionals.slice(1);
5686
5827
  if (positionalTarget !== undefined && option(arguments_, "target") !== undefined) {
@@ -5712,7 +5853,7 @@ var runCli = async (command, raw) => {
5712
5853
  ...workspace === undefined ? {} : { workspace },
5713
5854
  ...provider === undefined ? {} : { provider: Schema10.decodeUnknownSync(Provider)(provider) }
5714
5855
  }));
5715
- print(taskResult(arguments_.options.has("no-wait") ? task : await Effect9.runPromise(waitForTask(relay, task, timeout(arguments_)))));
5856
+ print(taskResult(arguments_.options.has("no-wait") ? task : await Effect9.runPromise(waitForTask(relay, task, waitSeconds))));
5716
5857
  return;
5717
5858
  }
5718
5859
  if (command === "status") {
@@ -5757,7 +5898,7 @@ var runCli = async (command, raw) => {
5757
5898
  init_src();
5758
5899
  import { Effect as Effect10, Schedule as Schedule3, Schema as Schema11 } from "effect";
5759
5900
  import { constants as constants3 } from "node:fs";
5760
- import { open as open3, realpath as realpath5, stat as stat4 } from "node:fs/promises";
5901
+ import { open as open3, realpath as realpath5, stat as stat5 } from "node:fs/promises";
5761
5902
  import { arch, hostname as hostname3, platform as platform4 } from "node:os";
5762
5903
  import { basename as basename2, isAbsolute as isAbsolute3, relative as relative2 } from "node:path";
5763
5904
  import { WebSocket as WebSocket2 } from "ws";
@@ -5862,7 +6003,7 @@ var openAllowedWorkspace = async (configuration, requested) => {
5862
6003
  }
5863
6004
  if (operatingSystem2 !== "linux") {
5864
6005
  const currentPath = await realpath5(path);
5865
- const currentPathIdentity = await stat4(currentPath);
6006
+ const currentPathIdentity = await stat5(currentPath);
5866
6007
  if (currentPathIdentity.dev !== identity.dev || currentPathIdentity.ino !== identity.ino) {
5867
6008
  throw new Error(`Workspace ${path} changed before provider startup`);
5868
6009
  }
@@ -6373,4 +6514,4 @@ await main().catch((cause) => {
6373
6514
  process.exitCode = 1;
6374
6515
  });
6375
6516
 
6376
- //# debugId=F147BDC9F8E0C7EE64756E2164756E21
6517
+ //# debugId=B9C02E5590F539C264756E2164756E21