@akshar5/cohall 0.5.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cohall.js CHANGED
@@ -16,10 +16,10 @@ var __export = (target, all) => {
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
18
  // packages/protocol/src/index.ts
19
- import { Schema } from "effect";
20
- var version = "0.5.6", 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$/, {
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$/, {
21
21
  expected: "an ISO-8601 UTC timestamp"
22
- })).pipe(Schema.brand(name)), 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) => {
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);
24
24
  if (match === null) {
25
25
  return;
@@ -50,10 +50,25 @@ var version = "0.5.6", maxSocketPayloadBytes, bounded = (maxLength) => Schema.St
50
50
  return;
51
51
  }
52
52
  throw new Error(`All-device upgrades require Cohall ${minimumDeviceOperationVersion} or newer on every device. Upgrade individually first: ${unsupported.map((device) => `${device.name} (${device.version})`).join(", ")}`);
53
- }, Thread, Message, Task, upgradeVersion, CreateUpgradeOperationsInput, DeviceOperation, TaskStatusCounts, ProviderUsage, DeviceUsage, UsageSummary, TaskTraceEvent, TaskTrace, ThreadContext, CreateTaskInput, SocketEvent, ErrorResponse, terminalTaskStatuses, isTerminalTask = (task) => terminalTaskStatuses.has(task.status), now = () => Timestamp.make(new Date().toISOString()), makeDeviceId = () => DeviceId.make(crypto.randomUUID()), makeThreadId = () => ThreadId.make(crypto.randomUUID()), makeMessageId = () => MessageId.make(crypto.randomUUID()), makeTaskId = () => TaskId.make(crypto.randomUUID()), makeAuthSessionId = () => AuthSessionId.make(crypto.randomUUID()), makeOperationId = () => OperationId.make(crypto.randomUUID()), decodeCreatePairingInput, decodeExchangePairingInput, decodeCreateTaskInput, decodeCreateUpgradeOperationsInput, decodeDevice, decodeSocketEvent;
53
+ }, Thread, Message, validBotTarget = (input) => {
54
+ const provider = input.provider ?? (input.botId === undefined ? "codex" : "grok-bot");
55
+ if (provider === "grok-bot" && input.workspace !== undefined) {
56
+ return "Grok Bots use their own computer context; omit workspace";
57
+ }
58
+ return provider === "grok-bot" === (input.botId !== undefined) ? undefined : "The grok-bot provider requires botId; other providers cannot select a bot";
59
+ }, Task, taskSlot = (task) => task.provider === "grok-bot" ? `grok-bot:${task.botId}` : "cli", upgradeVersion, CreateUpgradeOperationsInput, DeviceOperation, TaskStatusCounts, ProviderUsage, DeviceUsage, UsageSummary, TaskTraceEvent, TaskTrace, ThreadContext, CreateTaskInput, SocketEvent, ErrorResponse, terminalTaskStatuses, isTerminalTask = (task) => terminalTaskStatuses.has(task.status), now = () => Timestamp.make(new Date().toISOString()), makeDeviceId = () => DeviceId.make(crypto.randomUUID()), makeThreadId = () => ThreadId.make(crypto.randomUUID()), makeMessageId = () => MessageId.make(crypto.randomUUID()), makeTaskId = () => TaskId.make(crypto.randomUUID()), makeAuthSessionId = () => AuthSessionId.make(crypto.randomUUID()), makeOperationId = () => OperationId.make(crypto.randomUUID()), decodeCreatePairingInput, decodeExchangePairingInput, decodeCreateTaskInput = (input) => Schema.decodeUnknownEffect(CreateTaskInput)(input).pipe(Effect.map((task) => ({
60
+ ...task,
61
+ provider: task.provider ?? (task.botId === undefined ? "codex" : "grok-bot")
62
+ }))), decodeCreateUpgradeOperationsInput, decodeDevice, decodeSocketEvent;
54
63
  var init_src = __esm(() => {
55
64
  maxSocketPayloadBytes = 1024 * 1024;
56
65
  count = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }));
66
+ BotId = bounded(256).pipe(Schema.brand("BotId"));
67
+ Bot = Schema.Struct({
68
+ id: BotId,
69
+ name: bounded(128),
70
+ description: Schema.optionalKey(optionalText(512))
71
+ });
57
72
  DeviceId = uuid("DeviceId");
58
73
  ThreadId = uuid("ThreadId");
59
74
  MessageId = uuid("MessageId");
@@ -62,7 +77,7 @@ var init_src = __esm(() => {
62
77
  OperationId = uuid("OperationId");
63
78
  Timestamp = isoTimestamp("Timestamp");
64
79
  Platform = Schema.Literals(["darwin", "linux", "windows", "unknown"]);
65
- Provider = Schema.Literals(["codex", "claude-code", "opencode"]);
80
+ Provider = Schema.Literals(["codex", "claude-code", "opencode", "grok-bot"]);
66
81
  DeviceStatus = Schema.Literals(["online", "busy", "offline"]);
67
82
  TaskStatus = Schema.Literals([
68
83
  "queued",
@@ -127,7 +142,8 @@ var init_src = __esm(() => {
127
142
  platform: Platform,
128
143
  architecture: bounded(64),
129
144
  status: DeviceStatus,
130
- providers: boundedArray(Provider, 3),
145
+ providers: boundedArray(Provider, Provider.literals.length),
146
+ bots: Schema.optionalKey(boundedArray(Bot, 256)),
131
147
  capabilities: boundedArray(Capability, 64),
132
148
  workspaces: boundedArray(Workspace, 64),
133
149
  version: bounded(32),
@@ -157,6 +173,7 @@ var init_src = __esm(() => {
157
173
  prompt: bounded(131072),
158
174
  context: Schema.optionalKey(optionalText(131072)),
159
175
  provider: Provider,
176
+ botId: Schema.optionalKey(BotId),
160
177
  status: TaskStatus,
161
178
  sourceDeviceId: Schema.optionalKey(DeviceId),
162
179
  targetDeviceId: DeviceId,
@@ -169,7 +186,7 @@ var init_src = __esm(() => {
169
186
  updatedAt: Timestamp,
170
187
  startedAt: Schema.optionalKey(Timestamp),
171
188
  completedAt: Schema.optionalKey(Timestamp)
172
- });
189
+ }).check(Schema.makeFilter(validBotTarget));
173
190
  upgradeVersion = Schema.String.check(Schema.isPattern(/^(?:latest|v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/, {
174
191
  expected: "latest or an exact semantic version"
175
192
  }));
@@ -208,12 +225,12 @@ var init_src = __esm(() => {
208
225
  deviceName: bounded(128),
209
226
  tasks: count,
210
227
  byStatus: TaskStatusCounts,
211
- byProvider: boundedArray(ProviderUsage, 3)
228
+ byProvider: boundedArray(ProviderUsage, Provider.literals.length)
212
229
  });
213
230
  UsageSummary = Schema.Struct({
214
231
  retainedTasks: count,
215
232
  byStatus: TaskStatusCounts,
216
- byProvider: boundedArray(ProviderUsage, 3),
233
+ byProvider: boundedArray(ProviderUsage, Provider.literals.length),
217
234
  devices: boundedArray(DeviceUsage, 256)
218
235
  });
219
236
  TaskTraceEvent = Schema.Struct({
@@ -226,6 +243,7 @@ var init_src = __esm(() => {
226
243
  threadId: ThreadId,
227
244
  status: TaskStatus,
228
245
  provider: Provider,
246
+ botId: Schema.optionalKey(BotId),
229
247
  sourceDeviceId: Schema.optionalKey(DeviceId),
230
248
  targetDevice: Device,
231
249
  parentTaskId: Schema.optionalKey(TaskId),
@@ -237,7 +255,7 @@ var init_src = __esm(() => {
237
255
  events: boundedArray(TaskTraceEvent, 100),
238
256
  truncated: Schema.Boolean,
239
257
  error: Schema.optionalKey(optionalText(16384))
240
- });
258
+ }).check(Schema.makeFilter(validBotTarget));
241
259
  ThreadContext = Schema.Struct({
242
260
  thread: Thread,
243
261
  messages: boundedArray(Message, 20),
@@ -250,15 +268,20 @@ var init_src = __esm(() => {
250
268
  prompt: bounded(131072),
251
269
  context: Schema.optionalKey(optionalText(131072)),
252
270
  provider: Schema.optionalKey(Provider),
271
+ botId: Schema.optionalKey(BotId),
253
272
  targetDeviceId: Schema.optionalKey(DeviceId),
254
273
  parentTaskId: Schema.optionalKey(TaskId),
255
274
  workspace: Schema.optionalKey(bounded(4096))
256
- });
275
+ }).check(Schema.makeFilter(validBotTarget));
257
276
  SocketEvent = Schema.TaggedUnion({
258
277
  Authenticate: { token: bounded(256) },
259
278
  Connected: { serverVersion: bounded(32), connectedAt: Timestamp },
260
279
  DeviceHello: { device: Device },
261
- DeviceHeartbeat: { deviceId: DeviceId, status: DeviceStatus },
280
+ DeviceHeartbeat: {
281
+ deviceId: DeviceId,
282
+ status: DeviceStatus,
283
+ bots: Schema.optionalKey(boundedArray(Bot, 256))
284
+ },
262
285
  TaskAssigned: { task: Task },
263
286
  TaskAccepted: { taskId: TaskId },
264
287
  TaskFinished: {
@@ -287,25 +310,24 @@ var init_src = __esm(() => {
287
310
  ]);
288
311
  decodeCreatePairingInput = Schema.decodeUnknownEffect(CreatePairingInput);
289
312
  decodeExchangePairingInput = Schema.decodeUnknownEffect(ExchangePairingInput);
290
- decodeCreateTaskInput = Schema.decodeUnknownEffect(CreateTaskInput);
291
313
  decodeCreateUpgradeOperationsInput = Schema.decodeUnknownEffect(CreateUpgradeOperationsInput);
292
314
  decodeDevice = Schema.decodeUnknownEffect(Device);
293
315
  decodeSocketEvent = Schema.decodeUnknownEffect(SocketEvent);
294
316
  });
295
317
 
296
318
  // apps/relay/src/config.ts
297
- import { Effect as Effect10, Schema as Schema10 } from "effect";
298
- import { access as access5, chmod as chmod6, mkdir as mkdir6, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
299
- import { homedir as homedir5, platform as platform4 } from "node:os";
300
- import { join as join7, resolve as resolve3 } from "node:path";
319
+ import { Effect as Effect12, Schema as Schema12 } from "effect";
320
+ import { access as access5, chmod as chmod7, mkdir as mkdir7, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
321
+ import { homedir as homedir5, platform as platform5 } from "node:os";
322
+ import { join as join8, resolve as resolve4 } from "node:path";
301
323
  var RelayConfiguration, defaultDataDirectory = () => {
302
- if (platform4() === "win32") {
303
- return join7(process.env.LOCALAPPDATA ?? join7(homedir5(), "AppData", "Local"), "Cohall");
324
+ if (platform5() === "win32") {
325
+ return join8(process.env.LOCALAPPDATA ?? join8(homedir5(), "AppData", "Local"), "Cohall");
304
326
  }
305
- if (platform4() === "darwin") {
306
- return join7(homedir5(), "Library", "Application Support", "Cohall", "relay");
327
+ if (platform5() === "darwin") {
328
+ return join8(homedir5(), "Library", "Application Support", "Cohall", "relay");
307
329
  }
308
- return join7(process.env.XDG_DATA_HOME ?? join7(homedir5(), ".local", "share"), "cohall");
330
+ return join8(process.env.XDG_DATA_HOME ?? join8(homedir5(), ".local", "share"), "cohall");
309
331
  }, generatedToken = () => Array.from(crypto.getRandomValues(new Uint8Array(32)), (value) => value.toString(16).padStart(2, "0")).join(""), tokenFrom = async (path) => {
310
332
  if (await access5(path).then(() => true).catch(() => false)) {
311
333
  return (await readFile5(path, "utf8")).trim();
@@ -313,8 +335,8 @@ var RelayConfiguration, defaultDataDirectory = () => {
313
335
  const token = generatedToken();
314
336
  await writeFile7(path, `${token}
315
337
  `, { mode: 384 });
316
- if (platform4() !== "win32") {
317
- await chmod6(path, 384);
338
+ if (platform5() !== "win32") {
339
+ await chmod7(path, 384);
318
340
  }
319
341
  return token;
320
342
  }, booleanEnvironment = (name) => {
@@ -328,16 +350,16 @@ var RelayConfiguration, defaultDataDirectory = () => {
328
350
  throw new Error(`${name} must be true or false`);
329
351
  }, isLoopback2 = (host) => host === "127.0.0.1" || host === "localhost" || host === "::1", loadEnvironmentConfiguration;
330
352
  var init_config = __esm(() => {
331
- RelayConfiguration = Schema10.Struct({
332
- host: Schema10.NonEmptyString,
333
- port: Schema10.Int.check(Schema10.isBetween({ minimum: 1, maximum: 65535 })),
334
- token: Schema10.String.check(Schema10.isMinLength(32)),
335
- dataDirectory: Schema10.NonEmptyString,
336
- databasePath: Schema10.NonEmptyString,
337
- allowRemote: Schema10.Boolean,
338
- historyTaskLimit: Schema10.Int.check(Schema10.isBetween({ minimum: 100, maximum: 1e5 }))
353
+ RelayConfiguration = Schema12.Struct({
354
+ host: Schema12.NonEmptyString,
355
+ port: Schema12.Int.check(Schema12.isBetween({ minimum: 1, maximum: 65535 })),
356
+ token: Schema12.String.check(Schema12.isMinLength(32)),
357
+ dataDirectory: Schema12.NonEmptyString,
358
+ databasePath: Schema12.NonEmptyString,
359
+ allowRemote: Schema12.Boolean,
360
+ historyTaskLimit: Schema12.Int.check(Schema12.isBetween({ minimum: 100, maximum: 1e5 }))
339
361
  });
340
- loadEnvironmentConfiguration = Effect10.tryPromise({
362
+ loadEnvironmentConfiguration = Effect12.tryPromise({
341
363
  try: async () => {
342
364
  const host = process.env.COHALL_RELAY_HOST ?? "127.0.0.1";
343
365
  const port = Number(process.env.COHALL_RELAY_PORT ?? "8787");
@@ -345,22 +367,22 @@ var init_config = __esm(() => {
345
367
  if (!isLoopback2(host) && !allowRemote) {
346
368
  throw new Error("Remote relay binding is disabled. Use a private network or TLS reverse proxy, then set COHALL_RELAY_ALLOW_REMOTE=true.");
347
369
  }
348
- const dataDirectory = resolve3(process.env.COHALL_DATA_DIR ?? defaultDataDirectory());
349
- await mkdir6(dataDirectory, { recursive: true, mode: 448 });
350
- if (platform4() !== "win32") {
351
- await chmod6(dataDirectory, 448);
370
+ const dataDirectory = resolve4(process.env.COHALL_DATA_DIR ?? defaultDataDirectory());
371
+ await mkdir7(dataDirectory, { recursive: true, mode: 448 });
372
+ if (platform5() !== "win32") {
373
+ await chmod7(dataDirectory, 448);
352
374
  }
353
- const token = process.env.COHALL_TOKEN ?? await tokenFrom(join7(dataDirectory, "owner-token"));
375
+ const token = process.env.COHALL_TOKEN ?? await tokenFrom(join8(dataDirectory, "owner-token"));
354
376
  if (token.length < 32) {
355
377
  throw new Error("The Cohall relay owner token must be at least 32 characters");
356
378
  }
357
379
  const historyTaskLimit = Number(process.env.COHALL_HISTORY_TASK_LIMIT ?? "1000");
358
- return Schema10.decodeUnknownSync(RelayConfiguration)({
380
+ return Schema12.decodeUnknownSync(RelayConfiguration)({
359
381
  host,
360
382
  port,
361
383
  token,
362
384
  dataDirectory,
363
- databasePath: join7(dataDirectory, "cohall.db"),
385
+ databasePath: join8(dataDirectory, "cohall.db"),
364
386
  allowRemote,
365
387
  historyTaskLimit
366
388
  });
@@ -540,18 +562,18 @@ __export(exports_store, {
540
562
  RelayStore: () => exports_store,
541
563
  PersistenceError: () => PersistenceError
542
564
  });
543
- import { Context as Context2, Effect as Effect11, Layer as Layer2, Schema as Schema11 } from "effect";
565
+ import { Context as Context2, Effect as Effect13, Layer as Layer2, Schema as Schema13 } from "effect";
544
566
  import { createHash as createHash2, randomBytes } from "node:crypto";
545
567
  var PersistenceError, Service2, operationError = (operation) => (cause) => new PersistenceError({
546
568
  operation,
547
569
  message: cause instanceof Error ? cause.message : String(cause)
548
- }), decode = (operation, schema, value) => Schema11.decodeUnknownEffect(schema)(value).pipe(Effect11.mapError(operationError(operation))), threadFromRow = (row) => decode("RelayStore.decodeThread", Thread, {
570
+ }), decode2 = (operation, schema, value) => Schema13.decodeUnknownEffect(schema)(value).pipe(Effect13.mapError(operationError(operation))), threadFromRow = (row) => decode2("RelayStore.decodeThread", Thread, {
549
571
  id: row.id,
550
572
  title: row.title,
551
573
  createdAt: row.created_at,
552
574
  updatedAt: row.updated_at,
553
575
  ...row.default_device_id === null ? {} : { defaultDeviceId: row.default_device_id }
554
- }), messageFromRow = (row) => decode("RelayStore.decodeMessage", Message, {
576
+ }), messageFromRow = (row) => decode2("RelayStore.decodeMessage", Message, {
555
577
  id: row.id,
556
578
  threadId: row.thread_id,
557
579
  role: row.role === "tool" ? "agent" : row.role,
@@ -560,7 +582,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
560
582
  createdAt: row.created_at,
561
583
  ...row.task_id === null ? {} : { taskId: row.task_id },
562
584
  ...row.device_id === null ? {} : { deviceId: row.device_id }
563
- }), deviceFromRow = (row) => decode("RelayStore.decodeDevice", Device, {
585
+ }), deviceFromRow = (row) => decode2("RelayStore.decodeDevice", Device, {
564
586
  id: row.id,
565
587
  name: row.name,
566
588
  hostname: row.hostname,
@@ -568,16 +590,18 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
568
590
  architecture: row.architecture,
569
591
  status: row.status,
570
592
  providers: JSON.parse(row.providers_json),
593
+ ...row.bots_json === null ? {} : { bots: JSON.parse(row.bots_json) },
571
594
  capabilities: JSON.parse(row.capabilities_json),
572
595
  workspaces: JSON.parse(row.workspaces_json),
573
596
  version: row.version,
574
597
  lastSeenAt: row.last_seen_at,
575
598
  ...row.connected_at === null ? {} : { connectedAt: row.connected_at }
576
- }), taskFromRow = (row) => decode("RelayStore.decodeTask", Task, {
599
+ }), taskFromRow = (row) => decode2("RelayStore.decodeTask", Task, {
577
600
  id: row.id,
578
601
  threadId: row.thread_id,
579
602
  prompt: row.prompt,
580
603
  provider: row.provider,
604
+ ...row.bot_id === null ? {} : { botId: row.bot_id },
581
605
  status: row.status,
582
606
  targetDeviceId: row.target_device_id,
583
607
  createdAt: row.created_at,
@@ -591,7 +615,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
591
615
  ...row.error === null ? {} : { error: row.error },
592
616
  ...row.started_at === null ? {} : { startedAt: row.started_at },
593
617
  ...row.completed_at === null ? {} : { completedAt: row.completed_at }
594
- }), operationFromRow = (row) => decode("RelayStore.decodeOperation", DeviceOperation, {
618
+ }), operationFromRow = (row) => decode2("RelayStore.decodeOperation", DeviceOperation, {
595
619
  id: row.id,
596
620
  kind: row.kind,
597
621
  status: row.status,
@@ -611,17 +635,17 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
611
635
  completed: 0,
612
636
  failed: 0,
613
637
  cancelled: 0
614
- }), taskTraceEventFromRow = (row) => decode("RelayStore.decodeTaskTraceEvent", TaskTraceEvent, {
638
+ }), taskTraceEventFromRow = (row) => decode2("RelayStore.decodeTaskTraceEvent", TaskTraceEvent, {
615
639
  kind: row.kind,
616
640
  at: row.created_at,
617
641
  ...row.detail === null ? {} : { detail: row.detail }
618
- }), rolesFromRow = (row) => Schema11.decodeUnknownSync(Schema11.Array(ConnectionRole))(JSON.parse(row.roles_json)), authSessionFromRow = (row) => {
642
+ }), rolesFromRow = (row) => Schema13.decodeUnknownSync(Schema13.Array(ConnectionRole))(JSON.parse(row.roles_json)), authSessionFromRow = (row) => {
619
643
  const roles = rolesFromRow(row);
620
644
  const role = roles[0];
621
- return role === undefined ? Effect11.fail(new PersistenceError({
645
+ return role === undefined ? Effect13.fail(new PersistenceError({
622
646
  operation: "RelayStore.decodeAuthSession",
623
647
  message: `Session ${row.id} has no role`
624
- })) : decode("RelayStore.decodeAuthSession", AuthSession, {
648
+ })) : decode2("RelayStore.decodeAuthSession", AuthSession, {
625
649
  id: row.id,
626
650
  label: row.label,
627
651
  role,
@@ -641,6 +665,8 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
641
665
  return "Claude Code";
642
666
  case "opencode":
643
667
  return "OpenCode";
668
+ case "grok-bot":
669
+ return "Grok Bot";
644
670
  }
645
671
  }, recentWithin = (items, byteBudget) => {
646
672
  const encoder = new TextEncoder;
@@ -713,22 +739,22 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
713
739
  };
714
740
  const queryTask = (taskId) => db.query("SELECT * FROM tasks WHERE id = ?").get(taskId);
715
741
  const queryOperation = (operationId) => db.query("SELECT * FROM device_operations WHERE id = ?").get(operationId);
716
- const getTask = Effect11.fn("RelayStore.getTask")(function* (taskId) {
717
- const row = yield* Effect11.try({
742
+ const getTask = Effect13.fn("RelayStore.getTask")(function* (taskId) {
743
+ const row = yield* Effect13.try({
718
744
  try: () => queryTask(taskId),
719
745
  catch: operationError("RelayStore.getTask")
720
746
  });
721
747
  if (row === null) {
722
- return yield* Effect11.fail(new PersistenceError({
748
+ return yield* Effect13.fail(new PersistenceError({
723
749
  operation: "RelayStore.getTask",
724
750
  message: `Unknown task ${taskId}`
725
751
  }));
726
752
  }
727
753
  return yield* taskFromRow(row);
728
754
  });
729
- const traceTask = Effect11.fn("RelayStore.traceTask")(function* (taskId) {
755
+ const traceTask = Effect13.fn("RelayStore.traceTask")(function* (taskId) {
730
756
  const task = yield* getTask(taskId);
731
- const [deviceRow, eventRows] = yield* Effect11.try({
757
+ const [deviceRow, eventRows] = yield* Effect13.try({
732
758
  try: () => [
733
759
  db.query("SELECT * FROM devices WHERE id = ?").get(task.targetDeviceId),
734
760
  db.query(`SELECT * FROM (
@@ -739,20 +765,21 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
739
765
  catch: operationError("RelayStore.traceTask")
740
766
  });
741
767
  if (deviceRow === null) {
742
- return yield* Effect11.fail(new PersistenceError({
768
+ return yield* Effect13.fail(new PersistenceError({
743
769
  operation: "RelayStore.traceTask.device",
744
770
  message: `Unknown target device ${task.targetDeviceId}`
745
771
  }));
746
772
  }
747
773
  const targetDevice = yield* deviceFromRow(deviceRow);
748
774
  const selectedRows = eventRows.slice(-100);
749
- const recordedEvents = yield* Effect11.forEach(selectedRows, taskTraceEventFromRow);
775
+ const recordedEvents = yield* Effect13.forEach(selectedRows, taskTraceEventFromRow);
750
776
  const events = recordedEvents.length === 0 ? derivedTraceEvents(task) : recordedEvents;
751
777
  return TaskTrace.make({
752
778
  taskId: task.id,
753
779
  threadId: task.threadId,
754
780
  status: task.status,
755
781
  provider: task.provider,
782
+ ...task.botId === undefined ? {} : { botId: task.botId },
756
783
  targetDevice,
757
784
  createdAt: task.createdAt,
758
785
  updatedAt: task.updatedAt,
@@ -766,15 +793,15 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
766
793
  ...task.error === undefined ? {} : { error: task.error }
767
794
  });
768
795
  });
769
- const listDevices = Effect11.fn("RelayStore.listDevices")(function* () {
770
- const rows = yield* Effect11.try({
796
+ const listDevices = Effect13.fn("RelayStore.listDevices")(function* () {
797
+ const rows = yield* Effect13.try({
771
798
  try: () => db.query("SELECT * FROM devices WHERE forgotten_at IS NULL ORDER BY name COLLATE NOCASE, id").all(),
772
799
  catch: operationError("RelayStore.listDevices")
773
800
  });
774
- return yield* Effect11.forEach(rows, deviceFromRow);
801
+ return yield* Effect13.forEach(rows, deviceFromRow);
775
802
  });
776
- const usage = Effect11.fn("RelayStore.usage")(function* () {
777
- const rows = yield* Effect11.try({
803
+ const usage = Effect13.fn("RelayStore.usage")(function* () {
804
+ const rows = yield* Effect13.try({
778
805
  try: () => db.query(`SELECT tasks.target_device_id AS device_id, devices.name AS device_name,
779
806
  tasks.status, tasks.provider, COUNT(*) AS tasks
780
807
  FROM tasks
@@ -788,9 +815,9 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
788
815
  const devices = new Map;
789
816
  let retainedTasks = 0;
790
817
  for (const row of rows) {
791
- const status = Schema11.decodeUnknownSync(TaskStatus)(row.status);
792
- const provider = Schema11.decodeUnknownSync(Provider)(row.provider);
793
- const deviceId = Schema11.decodeUnknownSync(DeviceId)(row.device_id);
818
+ const status = Schema13.decodeUnknownSync(TaskStatus)(row.status);
819
+ const provider = Schema13.decodeUnknownSync(Provider)(row.provider);
820
+ const deviceId = Schema13.decodeUnknownSync(DeviceId)(row.device_id);
794
821
  retainedTasks += row.tasks;
795
822
  totalStatus[status] += row.tasks;
796
823
  totalProviders.set(provider, (totalProviders.get(provider) ?? 0) + row.tasks);
@@ -820,29 +847,29 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
820
847
  }))
821
848
  });
822
849
  });
823
- const getOperation = Effect11.fn("RelayStore.getOperation")(function* (operationId) {
824
- const row = yield* Effect11.try({
850
+ const getOperation = Effect13.fn("RelayStore.getOperation")(function* (operationId) {
851
+ const row = yield* Effect13.try({
825
852
  try: () => queryOperation(operationId),
826
853
  catch: operationError("RelayStore.getOperation")
827
854
  });
828
855
  if (row === null) {
829
- return yield* Effect11.fail(new PersistenceError({
856
+ return yield* Effect13.fail(new PersistenceError({
830
857
  operation: "RelayStore.getOperation",
831
858
  message: `Unknown device operation ${operationId}`
832
859
  }));
833
860
  }
834
861
  return yield* operationFromRow(row);
835
862
  });
836
- const listOperations = Effect11.fn("RelayStore.listOperations")(function* () {
837
- const rows = yield* Effect11.try({
863
+ const listOperations = Effect13.fn("RelayStore.listOperations")(function* () {
864
+ const rows = yield* Effect13.try({
838
865
  try: () => db.query("SELECT * FROM device_operations ORDER BY created_at DESC, id DESC LIMIT 50").all(),
839
866
  catch: operationError("RelayStore.listOperations")
840
867
  });
841
- return yield* Effect11.forEach(rows, operationFromRow);
868
+ return yield* Effect13.forEach(rows, operationFromRow);
842
869
  });
843
- const createUpgradeOperations = Effect11.fn("RelayStore.createUpgradeOperations")(function* (input) {
870
+ const createUpgradeOperations = Effect13.fn("RelayStore.createUpgradeOperations")(function* (input) {
844
871
  const timestamp = now();
845
- const operations = yield* Effect11.try({
872
+ const operations = yield* Effect13.try({
846
873
  try: () => db.transaction(() => {
847
874
  const devices = db.query("SELECT * FROM devices WHERE forgotten_at IS NULL ORDER BY name COLLATE NOCASE, id").all();
848
875
  if (devices.length === 0) {
@@ -882,15 +909,15 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
882
909
  });
883
910
  return operations;
884
911
  });
885
- const pendingOperationsFor = Effect11.fn("RelayStore.pendingOperationsFor")(function* (deviceId) {
886
- const rows = yield* Effect11.try({
912
+ const pendingOperationsFor = Effect13.fn("RelayStore.pendingOperationsFor")(function* (deviceId) {
913
+ const rows = yield* Effect13.try({
887
914
  try: () => db.query(`SELECT * FROM device_operations WHERE target_device_id = ? AND status = 'queued'
888
915
  ORDER BY created_at, id LIMIT 10`).all(deviceId),
889
916
  catch: operationError("RelayStore.pendingOperationsFor")
890
917
  });
891
- return yield* Effect11.forEach(rows, operationFromRow);
918
+ return yield* Effect13.forEach(rows, operationFromRow);
892
919
  });
893
- const transitionOperation = Effect11.fn("RelayStore.transitionOperation")(function* (operationId, expected, status, result, error) {
920
+ const transitionOperation = Effect13.fn("RelayStore.transitionOperation")(function* (operationId, expected, status, result, error) {
894
921
  const current = yield* getOperation(operationId);
895
922
  if (!expected.includes(current.status)) {
896
923
  return current;
@@ -898,7 +925,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
898
925
  const timestamp = now();
899
926
  const completed = status === "completed" || status === "failed" ? timestamp : undefined;
900
927
  const placeholders = expected.map(() => "?").join(", ");
901
- yield* Effect11.try({
928
+ yield* Effect13.try({
902
929
  try: () => db.transaction(() => {
903
930
  const updated = db.query(`UPDATE device_operations
904
931
  SET status = ?, result = ?, error = ?, updated_at = ?, completed_at = ?
@@ -911,23 +938,23 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
911
938
  });
912
939
  return yield* getOperation(operationId);
913
940
  });
914
- const requireOperationTarget = Effect11.fn("RelayStore.requireOperationTarget")(function* (operationId, deviceId) {
941
+ const requireOperationTarget = Effect13.fn("RelayStore.requireOperationTarget")(function* (operationId, deviceId) {
915
942
  const operation = yield* getOperation(operationId);
916
943
  if (operation.targetDeviceId !== deviceId) {
917
- return yield* Effect11.fail(new PersistenceError({
944
+ return yield* Effect13.fail(new PersistenceError({
918
945
  operation: "RelayStore.requireOperationTarget",
919
946
  message: `Device ${deviceId} cannot update operation ${operationId}`
920
947
  }));
921
948
  }
922
949
  return operation;
923
950
  });
924
- const assignOperation = Effect11.fn("RelayStore.assignOperation")(function* (operationId) {
951
+ const assignOperation = Effect13.fn("RelayStore.assignOperation")(function* (operationId) {
925
952
  const current = yield* getOperation(operationId);
926
953
  if (current.status !== "queued") {
927
954
  return current;
928
955
  }
929
956
  const timestamp = now();
930
- yield* Effect11.try({
957
+ yield* Effect13.try({
931
958
  try: () => db.query(`UPDATE device_operations SET status = 'assigned', updated_at = ?
932
959
  WHERE id = ? AND status = 'queued'
933
960
  AND NOT EXISTS (
@@ -943,19 +970,19 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
943
970
  });
944
971
  return yield* getOperation(operationId);
945
972
  });
946
- const acceptOperation = (operationId, deviceId) => requireOperationTarget(operationId, deviceId).pipe(Effect11.flatMap(() => transitionOperation(operationId, ["assigned"], "running")));
947
- const finishOperation = (operationId, deviceId, result) => requireOperationTarget(operationId, deviceId).pipe(Effect11.flatMap(() => transitionOperation(operationId, ["queued", "assigned", "running"], "completed", result)));
948
- const failOperation = (operationId, deviceId, error) => requireOperationTarget(operationId, deviceId).pipe(Effect11.flatMap(() => transitionOperation(operationId, ["queued", "assigned", "running"], "failed", undefined, error)));
973
+ const acceptOperation = (operationId, deviceId) => requireOperationTarget(operationId, deviceId).pipe(Effect13.flatMap(() => transitionOperation(operationId, ["assigned"], "running")));
974
+ const finishOperation = (operationId, deviceId, result) => requireOperationTarget(operationId, deviceId).pipe(Effect13.flatMap(() => transitionOperation(operationId, ["queued", "assigned", "running"], "completed", result)));
975
+ const failOperation = (operationId, deviceId, error) => requireOperationTarget(operationId, deviceId).pipe(Effect13.flatMap(() => transitionOperation(operationId, ["queued", "assigned", "running"], "failed", undefined, error)));
949
976
  const abandonOperation = (operationId) => transitionOperation(operationId, ["queued", "assigned", "running"], "failed", undefined, "Abandoned by the relay owner");
950
- const requeueOperationsFor = Effect11.fn("RelayStore.requeueOperationsFor")(function* (deviceId) {
951
- yield* Effect11.try({
977
+ const requeueOperationsFor = Effect13.fn("RelayStore.requeueOperationsFor")(function* (deviceId) {
978
+ yield* Effect13.try({
952
979
  try: () => db.query(`UPDATE device_operations SET status = 'queued', updated_at = ?
953
980
  WHERE target_device_id = ? AND status IN ('assigned', 'running')`).run(now(), deviceId),
954
981
  catch: operationError("RelayStore.requeueOperationsFor")
955
982
  });
956
983
  });
957
- const forgetDevice = Effect11.fn("RelayStore.forgetDevice")(function* (deviceId) {
958
- const row = yield* Effect11.try({
984
+ const forgetDevice = Effect13.fn("RelayStore.forgetDevice")(function* (deviceId) {
985
+ const row = yield* Effect13.try({
959
986
  try: () => db.transaction(() => {
960
987
  const device = db.query("SELECT * FROM devices WHERE id = ? AND forgotten_at IS NULL").get(deviceId);
961
988
  if (device === null) {
@@ -982,8 +1009,8 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
982
1009
  });
983
1010
  return yield* deviceFromRow(row);
984
1011
  });
985
- const recover = Effect11.fn("RelayStore.recover")(function* () {
986
- yield* Effect11.try({
1012
+ const recover = Effect13.fn("RelayStore.recover")(function* () {
1013
+ yield* Effect13.try({
987
1014
  try: () => db.transaction(() => {
988
1015
  const timestamp = now();
989
1016
  const interrupted = db.query("SELECT id FROM tasks WHERE status IN ('assigned', 'running')").all();
@@ -1001,52 +1028,54 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1001
1028
  catch: operationError("RelayStore.recover")
1002
1029
  });
1003
1030
  });
1004
- const upsertDevice = Effect11.fn("RelayStore.upsertDevice")(function* (device) {
1005
- yield* Effect11.try({
1031
+ const upsertDevice = Effect13.fn("RelayStore.upsertDevice")(function* (device) {
1032
+ yield* Effect13.try({
1006
1033
  try: () => db.query(`INSERT INTO devices (
1007
1034
  id, name, hostname, platform, architecture, status, providers_json,
1008
- capabilities_json, workspaces_json, version, last_seen_at, connected_at
1009
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1035
+ capabilities_json, workspaces_json, version, last_seen_at, connected_at, bots_json
1036
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1010
1037
  ON CONFLICT(id) DO UPDATE SET
1011
1038
  name = excluded.name, hostname = excluded.hostname, platform = excluded.platform,
1012
1039
  architecture = excluded.architecture, status = excluded.status,
1013
- providers_json = excluded.providers_json, capabilities_json = excluded.capabilities_json,
1040
+ providers_json = excluded.providers_json, bots_json = excluded.bots_json,
1041
+ capabilities_json = excluded.capabilities_json,
1014
1042
  workspaces_json = excluded.workspaces_json, version = excluded.version,
1015
1043
  last_seen_at = excluded.last_seen_at, connected_at = excluded.connected_at,
1016
- forgotten_at = NULL`).run(device.id, device.name, device.hostname, device.platform, device.architecture, device.status, JSON.stringify(device.providers), JSON.stringify(device.capabilities), JSON.stringify(device.workspaces), device.version, device.lastSeenAt, device.connectedAt ?? null),
1044
+ forgotten_at = NULL`).run(device.id, device.name, device.hostname, device.platform, device.architecture, device.status, JSON.stringify(device.providers), JSON.stringify(device.capabilities), JSON.stringify(device.workspaces), device.version, device.lastSeenAt, device.connectedAt ?? null, device.bots === undefined ? null : JSON.stringify(device.bots)),
1017
1045
  catch: operationError("RelayStore.upsertDevice")
1018
1046
  });
1019
1047
  return device;
1020
1048
  });
1021
- const updateDeviceStatus = Effect11.fn("RelayStore.updateDeviceStatus")(function* (deviceId, status) {
1049
+ const updateDeviceStatus = Effect13.fn("RelayStore.updateDeviceStatus")(function* (deviceId, status, bots) {
1022
1050
  const timestamp = now();
1023
- const result = yield* Effect11.try({
1024
- try: () => db.query("UPDATE devices SET status = ?, last_seen_at = ? WHERE id = ?").run(status, timestamp, deviceId),
1051
+ const result = yield* Effect13.try({
1052
+ try: () => db.query("UPDATE devices SET status = ?, last_seen_at = ?, bots_json = COALESCE(?, bots_json) WHERE id = ?").run(status, timestamp, bots === undefined ? null : JSON.stringify(bots), deviceId),
1025
1053
  catch: operationError("RelayStore.updateDeviceStatus")
1026
1054
  });
1027
1055
  if (result.changes !== 1) {
1028
- return yield* Effect11.fail(new PersistenceError({
1056
+ return yield* Effect13.fail(new PersistenceError({
1029
1057
  operation: "RelayStore.updateDeviceStatus",
1030
1058
  message: `Unknown device ${deviceId}`
1031
1059
  }));
1032
1060
  }
1033
1061
  const row = db.query("SELECT * FROM devices WHERE id = ?").get(deviceId);
1034
1062
  if (row === null) {
1035
- return yield* Effect11.fail(new PersistenceError({
1063
+ return yield* Effect13.fail(new PersistenceError({
1036
1064
  operation: "RelayStore.updateDeviceStatus.read",
1037
1065
  message: `Device ${deviceId} disappeared after update`
1038
1066
  }));
1039
1067
  }
1040
1068
  return yield* deviceFromRow(row);
1041
1069
  });
1042
- const createDelegation2 = Effect11.fn("RelayStore.createDelegation")(function* (input, targetDeviceId, sourceDeviceId, providerSessionId) {
1070
+ const createDelegation2 = Effect13.fn("RelayStore.createDelegation")(function* (input, targetDeviceId, sourceDeviceId, providerSessionId) {
1043
1071
  const timestamp = now();
1044
1072
  const threadId = input.threadId ?? makeThreadId();
1045
1073
  const task = Task.make({
1046
1074
  id: makeTaskId(),
1047
1075
  threadId,
1048
1076
  prompt: input.prompt,
1049
- provider: input.provider ?? "codex",
1077
+ provider: input.provider ?? (input.botId === undefined ? "codex" : "grok-bot"),
1078
+ ...input.botId === undefined ? {} : { botId: input.botId },
1050
1079
  status: "queued",
1051
1080
  targetDeviceId,
1052
1081
  createdAt: timestamp,
@@ -1073,7 +1102,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1073
1102
  taskId: task.id,
1074
1103
  ...sourceDeviceId === undefined ? {} : { deviceId: sourceDeviceId }
1075
1104
  });
1076
- yield* Effect11.try({
1105
+ yield* Effect13.try({
1077
1106
  try: () => db.transaction(() => {
1078
1107
  const outstanding = db.query(`SELECT COUNT(*) AS count FROM tasks WHERE target_device_id = ?
1079
1108
  AND status IN ('queued', 'assigned', 'running', 'cancelling')`).get(targetDeviceId);
@@ -1087,9 +1116,9 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1087
1116
  }
1088
1117
  db.query(`INSERT INTO tasks (
1089
1118
  id, thread_id, prompt, context, provider, status, source_device_id,
1090
- target_device_id, parent_task_id, workspace, provider_session_id,
1119
+ target_device_id, parent_task_id, workspace, provider_session_id, bot_id,
1091
1120
  created_at, updated_at
1092
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(task.id, task.threadId, task.prompt, task.context ?? null, task.provider, task.status, task.sourceDeviceId ?? null, task.targetDeviceId, task.parentTaskId ?? null, task.workspace ?? null, task.providerSessionId ?? null, task.createdAt, task.updatedAt);
1121
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(task.id, task.threadId, task.prompt, task.context ?? null, task.provider, task.status, task.sourceDeviceId ?? null, task.targetDeviceId, task.parentTaskId ?? null, task.workspace ?? null, task.providerSessionId ?? null, task.botId ?? null, task.createdAt, task.updatedAt);
1093
1122
  recordTaskTraceEvent(task.id, "queued", "Relay accepted the task", timestamp);
1094
1123
  db.query(`INSERT INTO messages (
1095
1124
  id, thread_id, role, kind, author_id, author_name, content, created_at,
@@ -1101,18 +1130,18 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1101
1130
  });
1102
1131
  return task;
1103
1132
  });
1104
- const threadContext2 = Effect11.fn("RelayStore.threadContext")(function* (threadId) {
1105
- const threadRow = yield* Effect11.try({
1133
+ const threadContext2 = Effect13.fn("RelayStore.threadContext")(function* (threadId) {
1134
+ const threadRow = yield* Effect13.try({
1106
1135
  try: () => db.query("SELECT * FROM threads WHERE id = ?").get(threadId),
1107
1136
  catch: operationError("RelayStore.threadContext.thread")
1108
1137
  });
1109
1138
  if (threadRow === null) {
1110
- return yield* Effect11.fail(new PersistenceError({
1139
+ return yield* Effect13.fail(new PersistenceError({
1111
1140
  operation: "RelayStore.threadContext",
1112
1141
  message: `Unknown thread ${threadId}`
1113
1142
  }));
1114
1143
  }
1115
- const [messageRows, taskRows] = yield* Effect11.try({
1144
+ const [messageRows, taskRows] = yield* Effect13.try({
1116
1145
  try: () => [
1117
1146
  db.query(`SELECT * FROM (
1118
1147
  SELECT * FROM messages WHERE thread_id = ?
@@ -1125,10 +1154,10 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1125
1154
  ],
1126
1155
  catch: operationError("RelayStore.threadContext.items")
1127
1156
  });
1128
- const [thread, messages, tasks] = yield* Effect11.all([
1157
+ const [thread, messages, tasks] = yield* Effect13.all([
1129
1158
  threadFromRow(threadRow),
1130
- Effect11.forEach(messageRows.slice(-20), messageFromRow),
1131
- Effect11.forEach(taskRows.slice(-20), taskFromRow)
1159
+ Effect13.forEach(messageRows.slice(-20), messageFromRow),
1160
+ Effect13.forEach(taskRows.slice(-20), taskFromRow)
1132
1161
  ]);
1133
1162
  const recentMessages = recentWithin(messages, 256 * 1024);
1134
1163
  const recentTasks = recentWithin(tasks, 768 * 1024);
@@ -1139,22 +1168,22 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1139
1168
  truncated: messageRows.length > 20 || taskRows.length > 20 || recentMessages.truncated || recentTasks.truncated
1140
1169
  });
1141
1170
  });
1142
- const pendingTasksFor = Effect11.fn("RelayStore.pendingTasksFor")(function* (deviceId) {
1143
- const rows = yield* Effect11.try({
1171
+ const pendingTasksFor = Effect13.fn("RelayStore.pendingTasksFor")(function* (deviceId) {
1172
+ const rows = yield* Effect13.try({
1144
1173
  try: () => db.query(`SELECT * FROM tasks WHERE target_device_id = ?
1145
1174
  AND status IN ('queued', 'cancelling') ORDER BY created_at, id LIMIT 100`).all(deviceId),
1146
1175
  catch: operationError("RelayStore.pendingTasksFor")
1147
1176
  });
1148
- return yield* Effect11.forEach(rows, taskFromRow);
1177
+ return yield* Effect13.forEach(rows, taskFromRow);
1149
1178
  });
1150
- const transition = Effect11.fn("RelayStore.transition")(function* (taskId, expected, update, event) {
1179
+ const transition = Effect13.fn("RelayStore.transition")(function* (taskId, expected, update, event) {
1151
1180
  const current = yield* getTask(taskId);
1152
1181
  if (!expected.includes(current.status)) {
1153
1182
  return current;
1154
1183
  }
1155
1184
  const placeholders = expected.map(() => "?").join(", ");
1156
1185
  const timestamp = now();
1157
- yield* Effect11.try({
1186
+ yield* Effect13.try({
1158
1187
  try: () => db.transaction(() => {
1159
1188
  const result = db.query(`UPDATE tasks SET status = ?, provider_session_id = ?, result = ?, error = ?,
1160
1189
  started_at = ?, completed_at = ?, updated_at = ?
@@ -1170,24 +1199,26 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1170
1199
  });
1171
1200
  return yield* getTask(taskId);
1172
1201
  });
1173
- const assignTask = Effect11.fn("RelayStore.assignTask")(function* (taskId) {
1202
+ const assignTask = Effect13.fn("RelayStore.assignTask")(function* (taskId) {
1174
1203
  const current = yield* getTask(taskId);
1175
1204
  if (current.status !== "queued") {
1176
1205
  return current;
1177
1206
  }
1178
1207
  const timestamp = now();
1179
- yield* Effect11.try({
1208
+ yield* Effect13.try({
1180
1209
  try: () => db.transaction(() => {
1181
1210
  const result = db.query(`UPDATE tasks SET status = 'assigned', updated_at = ?
1182
1211
  WHERE id = ? AND status = 'queued' AND NOT EXISTS (
1183
1212
  SELECT 1 FROM tasks active
1184
1213
  WHERE active.target_device_id = ? AND active.id <> ?
1214
+ AND CASE WHEN active.provider = 'grok-bot'
1215
+ THEN 'grok-bot:' || active.bot_id ELSE 'cli' END = ?
1185
1216
  AND active.status IN ('assigned', 'running', 'cancelling')
1186
1217
  ) AND NOT EXISTS (
1187
1218
  SELECT 1 FROM device_operations active
1188
1219
  WHERE active.target_device_id = ?
1189
1220
  AND active.status IN ('assigned', 'running')
1190
- )`).run(timestamp, taskId, current.targetDeviceId, taskId, current.targetDeviceId);
1221
+ )`).run(timestamp, taskId, current.targetDeviceId, taskId, taskSlot(current), current.targetDeviceId);
1191
1222
  if (result.changes === 1) {
1192
1223
  recordTaskTraceEvent(taskId, "assigned", "Relay dispatched the task over the device WebSocket", timestamp);
1193
1224
  }
@@ -1196,32 +1227,32 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1196
1227
  });
1197
1228
  return yield* getTask(taskId);
1198
1229
  });
1199
- const requireTarget = Effect11.fn("RelayStore.requireTarget")(function* (taskId, deviceId) {
1230
+ const requireTarget = Effect13.fn("RelayStore.requireTarget")(function* (taskId, deviceId) {
1200
1231
  const task = yield* getTask(taskId);
1201
1232
  if (task.targetDeviceId !== deviceId) {
1202
- return yield* Effect11.fail(new PersistenceError({
1233
+ return yield* Effect13.fail(new PersistenceError({
1203
1234
  operation: "RelayStore.requireTarget",
1204
1235
  message: `Task ${taskId} belongs to another device`
1205
1236
  }));
1206
1237
  }
1207
1238
  return task;
1208
1239
  });
1209
- const terminal = Effect11.fn("RelayStore.terminal")(function* (taskId, deviceId, status, result, error, providerSessionId) {
1240
+ const terminal = Effect13.fn("RelayStore.terminal")(function* (taskId, deviceId, status, result, error, providerSessionId) {
1210
1241
  const current = yield* requireTarget(taskId, deviceId);
1211
1242
  if (["completed", "failed", "cancelled"].includes(current.status)) {
1212
1243
  return current;
1213
1244
  }
1214
1245
  const timestamp = now();
1215
- yield* Effect11.try({
1246
+ yield* Effect13.try({
1216
1247
  try: () => db.transaction(() => {
1217
1248
  const updated = db.query(`UPDATE tasks SET status = ?, result = ?, error = ?, provider_session_id = ?,
1218
1249
  completed_at = ?, updated_at = ? WHERE id = ?
1219
- AND status NOT IN ('completed', 'failed', 'cancelled')`).run(status, result ?? null, error ?? null, providerSessionId ?? current.providerSessionId ?? null, timestamp, timestamp, taskId);
1250
+ AND status NOT IN ('completed', 'failed', 'cancelled')`).run(status, result ?? null, error ?? null, current.provider === "grok-bot" ? null : providerSessionId ?? current.providerSessionId ?? null, timestamp, timestamp, taskId);
1220
1251
  if (updated.changes !== 1) {
1221
1252
  return;
1222
1253
  }
1223
1254
  recordTaskTraceEvent(taskId, status, status === "completed" ? "Target device completed the task" : status === "failed" ? "Target device reported a task failure" : "Target device acknowledged cancellation", timestamp);
1224
- if (providerSessionId !== undefined) {
1255
+ if (current.provider !== "grok-bot" && providerSessionId !== undefined) {
1225
1256
  db.query(`INSERT INTO provider_sessions (thread_id, device_id, provider, session_id, updated_at)
1226
1257
  VALUES (?, ?, ?, ?, ?)
1227
1258
  ON CONFLICT(thread_id, device_id, provider) DO UPDATE SET
@@ -1242,11 +1273,17 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1242
1273
  });
1243
1274
  return yield* getTask(taskId);
1244
1275
  });
1245
- const requestCancellation = Effect11.fn("RelayStore.requestCancellation")(function* (taskId) {
1276
+ const requestCancellation = Effect13.fn("RelayStore.requestCancellation")(function* (taskId) {
1246
1277
  const current = yield* getTask(taskId);
1247
1278
  if (["completed", "failed", "cancelled"].includes(current.status)) {
1248
1279
  return current;
1249
1280
  }
1281
+ if (current.provider === "grok-bot" && current.status !== "queued") {
1282
+ return yield* new PersistenceError({
1283
+ operation: "RelayStore.requestCancellation",
1284
+ message: "Stop this bot in Grok Bot; its gateway cannot cancel an individual Cohall request"
1285
+ });
1286
+ }
1250
1287
  if (current.status === "cancelling") {
1251
1288
  return current;
1252
1289
  }
@@ -1255,9 +1292,9 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1255
1292
  status: "cancelling"
1256
1293
  }, { kind: "cancelling", detail: "Client requested task cancellation" });
1257
1294
  });
1258
- const requeueTasksFor = Effect11.fn("RelayStore.requeueTasksFor")(function* (deviceId) {
1295
+ const requeueTasksFor = Effect13.fn("RelayStore.requeueTasksFor")(function* (deviceId) {
1259
1296
  const timestamp = now();
1260
- yield* Effect11.try({
1297
+ yield* Effect13.try({
1261
1298
  try: () => db.transaction(() => {
1262
1299
  const interrupted = db.query(`SELECT id FROM tasks WHERE target_device_id = ?
1263
1300
  AND status IN ('assigned', 'running')`).all(deviceId);
@@ -1270,42 +1307,42 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1270
1307
  catch: operationError("RelayStore.requeueTasksFor")
1271
1308
  });
1272
1309
  });
1273
- const sessionFor = Effect11.fn("RelayStore.sessionFor")(function* (threadId, deviceId, provider) {
1274
- const row = yield* Effect11.try({
1310
+ const sessionFor = Effect13.fn("RelayStore.sessionFor")(function* (threadId, deviceId, provider) {
1311
+ const row = yield* Effect13.try({
1275
1312
  try: () => db.query(`SELECT session_id FROM provider_sessions
1276
1313
  WHERE thread_id = ? AND device_id = ? AND provider = ?`).get(threadId, deviceId, provider),
1277
1314
  catch: operationError("RelayStore.sessionFor")
1278
1315
  });
1279
1316
  return row?.session_id;
1280
1317
  });
1281
- const createPairing = Effect11.fn("RelayStore.createPairing")(function* (input) {
1318
+ const createPairing = Effect13.fn("RelayStore.createPairing")(function* (input) {
1282
1319
  const roles = [...new Set(input.roles)];
1283
1320
  if (roles.length === 0) {
1284
- return yield* Effect11.fail(new PersistenceError({
1321
+ return yield* Effect13.fail(new PersistenceError({
1285
1322
  operation: "RelayStore.createPairing",
1286
1323
  message: "A pairing must allow at least one role"
1287
1324
  }));
1288
1325
  }
1289
1326
  const token = secret("pair");
1290
1327
  const expiresAt = new Date(Date.now() + 10 * 60 * 1000).toISOString();
1291
- yield* Effect11.try({
1328
+ yield* Effect13.try({
1292
1329
  try: () => db.query(`INSERT INTO auth_pairings (token_hash, label, roles_json, expires_at, created_at)
1293
1330
  VALUES (?, ?, ?, ?, ?)`).run(tokenHash(token), input.label, JSON.stringify(roles), expiresAt, now()),
1294
1331
  catch: operationError("RelayStore.createPairing")
1295
1332
  });
1296
1333
  return PairingCredential.make({ token, expiresAt: Timestamp.make(expiresAt) });
1297
1334
  });
1298
- const exchangePairing2 = Effect11.fn("RelayStore.exchangePairing")(function* (token) {
1335
+ const exchangePairing2 = Effect13.fn("RelayStore.exchangePairing")(function* (token) {
1299
1336
  const timestamp = now();
1300
1337
  const expiresAt = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString();
1301
- const created = yield* Effect11.try({
1338
+ const created = yield* Effect13.try({
1302
1339
  try: () => db.transaction(() => {
1303
1340
  const pairing = db.query(`SELECT * FROM auth_pairings
1304
1341
  WHERE token_hash = ? AND used_at IS NULL AND expires_at > ?`).get(tokenHash(token), timestamp);
1305
1342
  if (pairing === null) {
1306
1343
  throw new Error("Pairing credential is invalid, expired, or already used");
1307
1344
  }
1308
- const roles = Schema11.decodeUnknownSync(Schema11.Array(ConnectionRole))(JSON.parse(pairing.roles_json));
1345
+ const roles = Schema13.decodeUnknownSync(Schema13.Array(ConnectionRole))(JSON.parse(pairing.roles_json));
1309
1346
  const deviceId = roles.includes("device") ? makeDeviceId() : undefined;
1310
1347
  const consumed = db.query("UPDATE auth_pairings SET used_at = ? WHERE token_hash = ? AND used_at IS NULL").run(timestamp, pairing.token_hash);
1311
1348
  if (consumed.changes !== 1) {
@@ -1333,11 +1370,11 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1333
1370
  })(),
1334
1371
  catch: operationError("RelayStore.exchangePairing")
1335
1372
  });
1336
- const credentials = yield* Effect11.forEach(created, ({ token: sessionToken, row }) => authSessionFromRow(row).pipe(Effect11.map((session) => ({ token: sessionToken, session }))));
1373
+ const credentials = yield* Effect13.forEach(created, ({ token: sessionToken, row }) => authSessionFromRow(row).pipe(Effect13.map((session) => ({ token: sessionToken, session }))));
1337
1374
  return PairingResult.make({ credentials });
1338
1375
  });
1339
- const authenticateSession = Effect11.fn("RelayStore.authenticateSession")(function* (token, role) {
1340
- const row = yield* Effect11.try({
1376
+ const authenticateSession = Effect13.fn("RelayStore.authenticateSession")(function* (token, role) {
1377
+ const row = yield* Effect13.try({
1341
1378
  try: () => db.query(`SELECT * FROM auth_sessions
1342
1379
  WHERE token_hash = ? AND revoked_at IS NULL AND expires_at > ?`).get(tokenHash(token), now()),
1343
1380
  catch: operationError("RelayStore.authenticateSession")
@@ -1346,35 +1383,35 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1346
1383
  return;
1347
1384
  }
1348
1385
  const lastSeenAt = now();
1349
- yield* Effect11.try({
1386
+ yield* Effect13.try({
1350
1387
  try: () => db.query("UPDATE auth_sessions SET last_seen_at = ? WHERE id = ?").run(lastSeenAt, row.id),
1351
1388
  catch: operationError("RelayStore.authenticateSession.touch")
1352
1389
  });
1353
1390
  const session = yield* authSessionFromRow(row);
1354
1391
  return AuthSession.make({ ...session, lastSeenAt });
1355
1392
  });
1356
- const listAuthSessions = Effect11.fn("RelayStore.listAuthSessions")(function* () {
1357
- const rows = yield* Effect11.try({
1393
+ const listAuthSessions = Effect13.fn("RelayStore.listAuthSessions")(function* () {
1394
+ const rows = yield* Effect13.try({
1358
1395
  try: () => db.query("SELECT * FROM auth_sessions ORDER BY created_at DESC, id LIMIT 1000").all(),
1359
1396
  catch: operationError("RelayStore.listAuthSessions")
1360
1397
  });
1361
- return yield* Effect11.forEach(rows, authSessionFromRow);
1398
+ return yield* Effect13.forEach(rows, authSessionFromRow);
1362
1399
  });
1363
- const revokeAuthSession = Effect11.fn("RelayStore.revokeAuthSession")(function* (sessionId) {
1400
+ const revokeAuthSession = Effect13.fn("RelayStore.revokeAuthSession")(function* (sessionId) {
1364
1401
  const revokedAt = now();
1365
- const result = yield* Effect11.try({
1402
+ const result = yield* Effect13.try({
1366
1403
  try: () => db.query("UPDATE auth_sessions SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL").run(revokedAt, sessionId),
1367
1404
  catch: operationError("RelayStore.revokeAuthSession")
1368
1405
  });
1369
1406
  if (result.changes !== 1) {
1370
- return yield* Effect11.fail(new PersistenceError({
1407
+ return yield* Effect13.fail(new PersistenceError({
1371
1408
  operation: "RelayStore.revokeAuthSession",
1372
1409
  message: `Unknown or already revoked session ${sessionId}`
1373
1410
  }));
1374
1411
  }
1375
1412
  const row = db.query("SELECT * FROM auth_sessions WHERE id = ?").get(sessionId);
1376
1413
  if (row === null) {
1377
- return yield* Effect11.fail(new PersistenceError({
1414
+ return yield* Effect13.fail(new PersistenceError({
1378
1415
  operation: "RelayStore.revokeAuthSession.read",
1379
1416
  message: `Session ${sessionId} disappeared after revocation`
1380
1417
  }));
@@ -1387,7 +1424,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1387
1424
  usage,
1388
1425
  forgetDevice,
1389
1426
  upsertDevice,
1390
- heartbeat: (deviceId, status) => updateDeviceStatus(deviceId, status),
1427
+ heartbeat: (deviceId, status, bots) => updateDeviceStatus(deviceId, status, bots),
1391
1428
  markDeviceOffline: (deviceId) => updateDeviceStatus(deviceId, "offline"),
1392
1429
  createDelegation: createDelegation2,
1393
1430
  getTask,
@@ -1396,7 +1433,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1396
1433
  pendingTasksFor,
1397
1434
  assignTask,
1398
1435
  rollbackAssignment: (taskId) => transition(taskId, ["assigned"], { status: "queued" }, { kind: "requeued", detail: "Target device was unavailable during dispatch" }),
1399
- acceptTask: (taskId, deviceId) => requireTarget(taskId, deviceId).pipe(Effect11.flatMap(() => transition(taskId, ["assigned"], { status: "running", startedAt: now() }, { kind: "running", detail: "Target device accepted the task and started the provider" }))),
1436
+ 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" }))),
1400
1437
  finishTask: (taskId, deviceId, result, providerSessionId) => terminal(taskId, deviceId, "completed", result, undefined, providerSessionId),
1401
1438
  failTask: (taskId, deviceId, error) => terminal(taskId, deviceId, "failed", undefined, error),
1402
1439
  acknowledgeCancellation: (taskId, deviceId) => terminal(taskId, deviceId, "cancelled"),
@@ -1419,7 +1456,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1419
1456
  listAuthSessions,
1420
1457
  revokeAuthSession
1421
1458
  });
1422
- }, migrate = (db) => Effect11.try({
1459
+ }, migrate = (db) => Effect13.try({
1423
1460
  try: () => {
1424
1461
  db.exec("PRAGMA journal_mode = WAL");
1425
1462
  db.exec("PRAGMA foreign_keys = ON");
@@ -1428,7 +1465,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1428
1465
  CREATE TABLE IF NOT EXISTS devices (
1429
1466
  id TEXT PRIMARY KEY, name TEXT NOT NULL, hostname TEXT NOT NULL,
1430
1467
  platform TEXT NOT NULL, architecture TEXT NOT NULL, status TEXT NOT NULL,
1431
- providers_json TEXT NOT NULL, capabilities_json TEXT NOT NULL,
1468
+ providers_json TEXT NOT NULL, bots_json TEXT, capabilities_json TEXT NOT NULL,
1432
1469
  workspaces_json TEXT NOT NULL, version TEXT NOT NULL, last_seen_at TEXT NOT NULL,
1433
1470
  connected_at TEXT, forgotten_at TEXT
1434
1471
  );
@@ -1446,7 +1483,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1446
1483
  CREATE INDEX IF NOT EXISTS messages_thread_created ON messages(thread_id, created_at);
1447
1484
  CREATE TABLE IF NOT EXISTS tasks (
1448
1485
  id TEXT PRIMARY KEY, thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
1449
- prompt TEXT NOT NULL, context TEXT, provider TEXT NOT NULL, status TEXT NOT NULL,
1486
+ prompt TEXT NOT NULL, context TEXT, provider TEXT NOT NULL, bot_id TEXT, status TEXT NOT NULL,
1450
1487
  source_device_id TEXT REFERENCES devices(id),
1451
1488
  target_device_id TEXT NOT NULL REFERENCES devices(id), parent_task_id TEXT,
1452
1489
  workspace TEXT, provider_session_id TEXT, result TEXT, error TEXT,
@@ -1491,17 +1528,24 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
1491
1528
  if (!deviceColumns.some((column) => column.name === "forgotten_at")) {
1492
1529
  db.exec("ALTER TABLE devices ADD COLUMN forgotten_at TEXT");
1493
1530
  }
1531
+ if (!deviceColumns.some((column) => column.name === "bots_json")) {
1532
+ db.exec("ALTER TABLE devices ADD COLUMN bots_json TEXT");
1533
+ }
1534
+ const taskColumns = db.query("PRAGMA table_info(tasks)").all();
1535
+ if (!taskColumns.some((column) => column.name === "bot_id")) {
1536
+ db.exec("ALTER TABLE tasks ADD COLUMN bot_id TEXT");
1537
+ }
1494
1538
  },
1495
1539
  catch: operationError("RelayStore.migrate")
1496
- }), layer2 = (path, retainedTerminalTasks = 1000) => Layer2.effect(Service2, Effect11.acquireRelease(Effect11.try({
1540
+ }), layer2 = (path, retainedTerminalTasks = 1000) => Layer2.effect(Service2, Effect13.acquireRelease(Effect13.try({
1497
1541
  try: () => new Database(path),
1498
1542
  catch: operationError("RelayStore.open")
1499
- }).pipe(Effect11.tap(migrate)), (db) => Effect11.sync(() => db.close())).pipe(Effect11.map((db) => makeService(db, retainedTerminalTasks)))), layerFromDatabase = (db) => Layer2.effect(Service2, migrate(db).pipe(Effect11.map(() => makeService(db))));
1543
+ }).pipe(Effect13.tap(migrate)), (db) => Effect13.sync(() => db.close())).pipe(Effect13.map((db) => makeService(db, retainedTerminalTasks)))), layerFromDatabase = (db) => Layer2.effect(Service2, migrate(db).pipe(Effect13.map(() => makeService(db))));
1500
1544
  var init_store = __esm(() => {
1501
1545
  init_src();
1502
1546
  init_database();
1503
1547
  init_store();
1504
- PersistenceError = class PersistenceError extends Schema11.TaggedErrorClass()("RelayStore.PersistenceError", { operation: Schema11.String, message: Schema11.String }) {
1548
+ PersistenceError = class PersistenceError extends Schema13.TaggedErrorClass()("RelayStore.PersistenceError", { operation: Schema13.String, message: Schema13.String }) {
1505
1549
  };
1506
1550
  Service2 = class Service2 extends Context2.Service()("@cohall/RelayStore") {
1507
1551
  };
@@ -1511,13 +1555,14 @@ var init_store = __esm(() => {
1511
1555
  var exports_main = {};
1512
1556
  __export(exports_main, {
1513
1557
  runRelay: () => runRelay,
1514
- relayListenOptions: () => relayListenOptions
1558
+ relayListenOptions: () => relayListenOptions,
1559
+ chooseDevice: () => chooseDevice
1515
1560
  });
1516
- import { Effect as Effect12, ManagedRuntime, Schema as Schema12 } from "effect";
1561
+ import { Effect as Effect14, ManagedRuntime, Schema as Schema14 } from "effect";
1517
1562
  import { createHash as createHash3, timingSafeEqual } from "node:crypto";
1518
- import { access as access6, chmod as chmod7 } from "node:fs/promises";
1563
+ import { access as access6, chmod as chmod8 } from "node:fs/promises";
1519
1564
  import { createServer } from "node:http";
1520
- import { platform as platform5 } from "node:os";
1565
+ import { platform as platform6 } from "node:os";
1521
1566
  import { WebSocketServer } from "ws";
1522
1567
  var RequestError, relayListenOptions = (configuration, environment = process.env, pid = process.pid) => {
1523
1568
  const listenPid = Number(environment.LISTEN_PID);
@@ -1544,7 +1589,7 @@ var RequestError, relayListenOptions = (configuration, environment = process.env
1544
1589
  if (request.method === "GET" || request.method === "HEAD") {
1545
1590
  return Promise.resolve(undefined);
1546
1591
  }
1547
- return new Promise((resolve4, reject) => {
1592
+ return new Promise((resolve5, reject) => {
1548
1593
  const chunks = [];
1549
1594
  let size = 0;
1550
1595
  const cleanup = () => {
@@ -1563,7 +1608,7 @@ var RequestError, relayListenOptions = (configuration, environment = process.env
1563
1608
  };
1564
1609
  const onEnd = () => {
1565
1610
  cleanup();
1566
- resolve4(Buffer.concat(chunks, size));
1611
+ resolve5(Buffer.concat(chunks, size));
1567
1612
  };
1568
1613
  const onError = (cause) => {
1569
1614
  cleanup();
@@ -1612,18 +1657,18 @@ Content-Length: 0\r
1612
1657
  if ("destroy" in socket && typeof socket.destroy === "function") {
1613
1658
  socket.destroy();
1614
1659
  }
1615
- }, body = (request, decode2) => Effect12.tryPromise({
1660
+ }, body = (request, decode3) => Effect14.tryPromise({
1616
1661
  try: () => request.json(),
1617
1662
  catch: () => new RequestError({ status: 400, message: "Expected a JSON request body" })
1618
- }).pipe(Effect12.flatMap(decode2), Effect12.mapError((cause) => cause instanceof RequestError ? cause : new RequestError({ status: 400, message: String(cause).slice(0, 2048) }))), pathId = (schema, value) => Schema12.decodeUnknownEffect(schema)(value).pipe(Effect12.mapError(() => new RequestError({ status: 400, message: `Invalid identifier: ${value}` }))), scoreDevice = (device, input, sourceDeviceId) => {
1663
+ }).pipe(Effect14.flatMap(decode3), Effect14.mapError((cause) => cause instanceof RequestError ? cause : new RequestError({ status: 400, message: String(cause).slice(0, 2048) }))), pathId = (schema, value) => Schema14.decodeUnknownEffect(schema)(value).pipe(Effect14.mapError(() => new RequestError({ status: 400, message: `Invalid identifier: ${value}` }))), scoreDevice = (device, input, sourceDeviceId) => {
1619
1664
  const prompt = `${input.prompt} ${input.context ?? ""}`.toLowerCase();
1620
1665
  const needsXcode = /\b(xcode|ios|macos|swiftui|app store)\b/.test(prompt);
1621
1666
  const needsBrowser = /\b(browser|chrome|youtube|twitter|logged[- ]?in|signed[- ]?in|x\.com)\b/.test(prompt);
1622
1667
  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);
1623
- }, chooseDevice = (input, sourceDeviceId) => Effect12.gen(function* () {
1668
+ }, chooseDevice = (input, sourceDeviceId) => Effect14.gen(function* () {
1624
1669
  const store = yield* exports_store.Service;
1625
- const devices = yield* store.listDevices().pipe(Effect12.mapError((cause) => new RequestError({ status: 500, message: cause.message })));
1626
- const provider = input.provider ?? "codex";
1670
+ const devices = yield* store.listDevices().pipe(Effect14.mapError((cause) => new RequestError({ status: 500, message: cause.message })));
1671
+ const provider = input.provider ?? (input.botId === undefined ? "codex" : "grok-bot");
1627
1672
  if (input.targetDeviceId !== undefined) {
1628
1673
  const target = devices.find((device) => device.id === input.targetDeviceId);
1629
1674
  if (target === undefined) {
@@ -1638,27 +1683,40 @@ Content-Length: 0\r
1638
1683
  message: `${target.name} does not advertise the ${provider} provider`
1639
1684
  });
1640
1685
  }
1686
+ if (input.botId !== undefined && !target.bots?.some((bot) => bot.id === input.botId)) {
1687
+ return yield* new RequestError({
1688
+ status: 404,
1689
+ message: `${target.name} does not advertise bot ${input.botId}; refresh the bot list`
1690
+ });
1691
+ }
1641
1692
  return target.id;
1642
1693
  }
1643
- const selected = devices.filter((device) => device.providers.includes(provider)).sort((left, right) => scoreDevice(right, input, sourceDeviceId) - scoreDevice(left, input, sourceDeviceId))[0];
1694
+ const candidates = devices.filter((device) => device.providers.includes(provider) && (input.botId === undefined || device.bots?.some((bot) => bot.id === input.botId)));
1695
+ if (input.botId !== undefined && candidates.length > 1) {
1696
+ return yield* new RequestError({
1697
+ status: 409,
1698
+ message: `Bot ${input.botId} is advertised by more than one device; select a device`
1699
+ });
1700
+ }
1701
+ const selected = candidates.sort((left, right) => scoreDevice(right, input, sourceDeviceId) - scoreDevice(left, input, sourceDeviceId))[0];
1644
1702
  if (selected === undefined) {
1645
1703
  return yield* new RequestError({
1646
1704
  status: 409,
1647
- message: `No Cohall device advertises the ${provider} provider`
1705
+ message: input.botId === undefined ? `No Cohall device advertises the ${provider} provider` : `No Cohall device advertises bot ${input.botId}; refresh the bot list`
1648
1706
  });
1649
1707
  }
1650
1708
  return selected.id;
1651
1709
  }), runRelay = async () => {
1652
- const configuration = await Effect12.runPromise(loadEnvironmentConfiguration);
1710
+ const configuration = await Effect14.runPromise(loadEnvironmentConfiguration);
1653
1711
  process.umask(63);
1654
1712
  const runtime = ManagedRuntime.make(exports_store.layer(configuration.databasePath, configuration.historyTaskLimit));
1655
1713
  const run2 = (effect) => runtime.runPromise(effect);
1656
- await run2(Effect12.gen(function* () {
1714
+ await run2(Effect14.gen(function* () {
1657
1715
  const store = yield* exports_store.Service;
1658
1716
  yield* store.recover();
1659
1717
  }));
1660
- if (platform5() !== "win32" && await access6(configuration.databasePath).then(() => true).catch(() => false)) {
1661
- await chmod7(configuration.databasePath, 384);
1718
+ if (platform6() !== "win32" && await access6(configuration.databasePath).then(() => true).catch(() => false)) {
1719
+ await chmod8(configuration.databasePath, 384);
1662
1720
  }
1663
1721
  const hub = new Hub;
1664
1722
  let connections = 0;
@@ -1668,7 +1726,7 @@ Content-Length: 0\r
1668
1726
  if (ownerToken2(token)) {
1669
1727
  return "owner";
1670
1728
  }
1671
- return run2(Effect12.gen(function* () {
1729
+ return run2(Effect14.gen(function* () {
1672
1730
  const store = yield* exports_store.Service;
1673
1731
  return yield* store.authenticateSession(token, role);
1674
1732
  })).catch(() => {
@@ -1683,7 +1741,7 @@ Content-Length: 0\r
1683
1741
  return authenticate(authorization.slice("Bearer ".length), "client");
1684
1742
  };
1685
1743
  const dispatch = async (task) => {
1686
- const assigned = await run2(Effect12.gen(function* () {
1744
+ const assigned = await run2(Effect14.gen(function* () {
1687
1745
  const store = yield* exports_store.Service;
1688
1746
  return yield* store.assignTask(task.id);
1689
1747
  }));
@@ -1693,13 +1751,13 @@ Content-Length: 0\r
1693
1751
  if (hub.sendToDevice(assigned.targetDeviceId, SocketEvent.make({ _tag: "TaskAssigned", task: assigned }))) {
1694
1752
  return assigned;
1695
1753
  }
1696
- return run2(Effect12.gen(function* () {
1754
+ return run2(Effect14.gen(function* () {
1697
1755
  const store = yield* exports_store.Service;
1698
1756
  return yield* store.rollbackAssignment(task.id);
1699
1757
  }));
1700
1758
  };
1701
1759
  const dispatchOperation = async (operation) => {
1702
- const assigned = await run2(Effect12.gen(function* () {
1760
+ const assigned = await run2(Effect14.gen(function* () {
1703
1761
  const store = yield* exports_store.Service;
1704
1762
  return yield* store.assignOperation(operation.id);
1705
1763
  }));
@@ -1709,13 +1767,22 @@ Content-Length: 0\r
1709
1767
  if (hub.sendToDevice(assigned.targetDeviceId, SocketEvent.make({ _tag: "OperationAssigned", operation: assigned }))) {
1710
1768
  return assigned;
1711
1769
  }
1712
- return run2(Effect12.gen(function* () {
1770
+ return run2(Effect14.gen(function* () {
1713
1771
  const store = yield* exports_store.Service;
1714
1772
  return yield* store.rollbackOperation(operation.id);
1715
1773
  }));
1716
1774
  };
1717
- const dispatchPending = async (deviceId) => {
1718
- const tasks = await run2(Effect12.gen(function* () {
1775
+ const dispatchPendingOperations = async (deviceId) => {
1776
+ const operations = await run2(Effect14.gen(function* () {
1777
+ const store = yield* exports_store.Service;
1778
+ return yield* store.pendingOperationsFor(deviceId);
1779
+ }));
1780
+ for (const operation of operations) {
1781
+ await dispatchOperation(operation);
1782
+ }
1783
+ };
1784
+ const dispatchPendingTasks = async (deviceId) => {
1785
+ const tasks = await run2(Effect14.gen(function* () {
1719
1786
  const store = yield* exports_store.Service;
1720
1787
  return yield* store.pendingTasksFor(deviceId);
1721
1788
  }));
@@ -1726,13 +1793,10 @@ Content-Length: 0\r
1726
1793
  await dispatch(task);
1727
1794
  }
1728
1795
  }
1729
- const operations = await run2(Effect12.gen(function* () {
1730
- const store = yield* exports_store.Service;
1731
- return yield* store.pendingOperationsFor(deviceId);
1732
- }));
1733
- for (const operation of operations) {
1734
- await dispatchOperation(operation);
1735
- }
1796
+ };
1797
+ const dispatchPending = async (deviceId) => {
1798
+ await dispatchPendingOperations(deviceId);
1799
+ await dispatchPendingTasks(deviceId);
1736
1800
  };
1737
1801
  const sendError = (socket, code, message) => {
1738
1802
  socket.send(JSON.stringify(SocketEvent.make({
@@ -1742,10 +1806,10 @@ Content-Length: 0\r
1742
1806
  })));
1743
1807
  };
1744
1808
  const handleSocketEvent = async (socket, input) => {
1745
- const event = await Effect12.runPromise(Effect12.try({
1809
+ const event = await Effect14.runPromise(Effect14.try({
1746
1810
  try: () => JSON.parse(socketText(input)),
1747
1811
  catch: () => new RequestError({ status: 400, message: "Invalid socket JSON" })
1748
- }).pipe(Effect12.flatMap(decodeSocketEvent))).catch((cause) => {
1812
+ }).pipe(Effect14.flatMap(decodeSocketEvent))).catch((cause) => {
1749
1813
  sendError(socket, "invalid_event", cause instanceof Error ? cause.message : String(cause));
1750
1814
  return;
1751
1815
  });
@@ -1802,11 +1866,12 @@ Content-Length: 0\r
1802
1866
  socket.close(4009, "Device is already connected");
1803
1867
  return;
1804
1868
  }
1805
- await run2(Effect12.gen(function* () {
1869
+ await run2(Effect14.gen(function* () {
1806
1870
  const store = yield* exports_store.Service;
1807
1871
  yield* store.upsertDevice(device);
1808
1872
  }));
1809
- await dispatchPending(device.id);
1873
+ await dispatchPendingTasks(device.id);
1874
+ await dispatchPendingOperations(device.id);
1810
1875
  return;
1811
1876
  }
1812
1877
  const deviceId = hub.deviceId(socket);
@@ -1820,12 +1885,12 @@ Content-Length: 0\r
1820
1885
  const settleOperation = (operationId) => {
1821
1886
  socket.send(JSON.stringify(SocketEvent.make({ _tag: "OperationSettled", operationId })));
1822
1887
  };
1823
- const processed = await run2(Effect12.gen(function* () {
1888
+ const processed = await run2(Effect14.gen(function* () {
1824
1889
  const store = yield* exports_store.Service;
1825
1890
  switch (event._tag) {
1826
1891
  case "DeviceHeartbeat":
1827
1892
  if (event.deviceId === deviceId) {
1828
- yield* store.heartbeat(deviceId, event.status === "busy" ? "busy" : "online");
1893
+ yield* store.heartbeat(deviceId, event.status === "busy" ? "busy" : "online", event.bots);
1829
1894
  }
1830
1895
  return;
1831
1896
  case "TaskAccepted":
@@ -1876,7 +1941,7 @@ Content-Length: 0\r
1876
1941
  return json({ ok: true, version });
1877
1942
  }
1878
1943
  if (url.pathname === "/api/auth/pair" && request.method === "POST") {
1879
- return run2(Effect12.gen(function* () {
1944
+ return run2(Effect14.gen(function* () {
1880
1945
  const store = yield* exports_store.Service;
1881
1946
  const input = yield* body(request, decodeExchangePairingInput);
1882
1947
  return yield* store.exchangePairing(input.token);
@@ -1891,7 +1956,7 @@ Content-Length: 0\r
1891
1956
  return json({ error: "Owner token required" }, 403);
1892
1957
  }
1893
1958
  const sourceDeviceId = principal === "owner" ? undefined : principal.deviceId;
1894
- const effect = Effect12.gen(function* () {
1959
+ const effect = Effect14.gen(function* () {
1895
1960
  const store = yield* exports_store.Service;
1896
1961
  if (url.pathname === "/api/auth/pairings" && request.method === "POST") {
1897
1962
  const input = yield* body(request, decodeCreatePairingInput);
@@ -1919,7 +1984,7 @@ Content-Length: 0\r
1919
1984
  if (url.pathname === "/api/operations/upgrades" && request.method === "POST") {
1920
1985
  const input = yield* body(request, decodeCreateUpgradeOperationsInput);
1921
1986
  const operations = yield* store.createUpgradeOperations(input);
1922
- return json(yield* Effect12.tryPromise(() => Promise.all(operations.map(dispatchOperation))), 201);
1987
+ return json(yield* Effect14.tryPromise(() => Promise.all(operations.map(dispatchOperation))), 201);
1923
1988
  }
1924
1989
  const abandonOperation = url.pathname.match(/^\/api\/operations\/([^/]+)\/abandon$/);
1925
1990
  if (request.method === "POST" && abandonOperation?.[1] !== undefined) {
@@ -1946,9 +2011,9 @@ Content-Length: 0\r
1946
2011
  if (url.pathname === "/api/tasks" && request.method === "POST") {
1947
2012
  const input = yield* body(request, decodeCreateTaskInput);
1948
2013
  const target = yield* chooseDevice(input, sourceDeviceId);
1949
- const providerSessionId = input.threadId === undefined ? undefined : yield* store.sessionFor(input.threadId, target, input.provider ?? "codex");
2014
+ const providerSessionId = input.threadId === undefined || input.provider === "grok-bot" ? undefined : yield* store.sessionFor(input.threadId, target, input.provider ?? "codex");
1950
2015
  const task2 = yield* store.createDelegation(input, target, sourceDeviceId, providerSessionId);
1951
- return json(yield* Effect12.tryPromise(() => dispatch(task2)), 201);
2016
+ return json(yield* Effect14.tryPromise(() => dispatch(task2)), 201);
1952
2017
  }
1953
2018
  const task = url.pathname.match(/^\/api\/tasks\/([^/]+)$/);
1954
2019
  if (request.method === "GET" && task?.[1] !== undefined) {
@@ -1972,8 +2037,8 @@ Content-Length: 0\r
1972
2037
  return json(yield* store.threadContext(yield* pathId(ThreadId, thread[1])));
1973
2038
  }
1974
2039
  return yield* new RequestError({ status: 404, message: "Route not found" });
1975
- }).pipe(Effect12.mapError((cause) => cause instanceof RequestError ? cause : new RequestError({
1976
- status: cause._tag !== "RelayStore.PersistenceError" ? 500 : cause.message.startsWith("Unknown") ? 404 : cause.message.includes("outstanding task limit") ? 429 : cause.message.includes("must be offline") || cause.message.includes("still has outstanding tasks") || cause.message.includes("upgrade operation in progress") || cause.message === "No Cohall devices are registered" || cause.message.startsWith("All-device operations support") ? 409 : 500,
2040
+ }).pipe(Effect14.mapError((cause) => cause instanceof RequestError ? cause : new RequestError({
2041
+ status: cause._tag !== "RelayStore.PersistenceError" ? 500 : cause.message.startsWith("Unknown") ? 404 : cause.message.includes("cannot cancel an individual Cohall request") ? 409 : cause.message.includes("outstanding task limit") ? 429 : cause.message.includes("must be offline") || cause.message.includes("still has outstanding tasks") || cause.message.includes("upgrade operation in progress") || cause.message === "No Cohall devices are registered" || cause.message.startsWith("All-device operations support") ? 409 : 500,
1977
2042
  message: cause.message
1978
2043
  })));
1979
2044
  return run2(effect).catch((cause) => cause instanceof RequestError ? json({ error: cause.status === 500 ? "Relay request failed" : cause.message }, cause.status) : json({ error: "Relay request failed" }, 500));
@@ -1981,7 +2046,8 @@ Content-Length: 0\r
1981
2046
  const websocketServer = new WebSocketServer({
1982
2047
  noServer: true,
1983
2048
  maxPayload: maxSocketPayloadBytes,
1984
- perMessageDeflate: false
2049
+ perMessageDeflate: false,
2050
+ allowSynchronousEvents: false
1985
2051
  });
1986
2052
  websocketServer.on("connection", (rawSocket) => {
1987
2053
  const socket = rawSocket;
@@ -2046,7 +2112,7 @@ Content-Length: 0\r
2046
2112
  if (deviceId === undefined) {
2047
2113
  return;
2048
2114
  }
2049
- run2(Effect12.gen(function* () {
2115
+ run2(Effect14.gen(function* () {
2050
2116
  const store = yield* exports_store.Service;
2051
2117
  yield* store.requeueTasksFor(deviceId);
2052
2118
  yield* store.requeueOperationsFor(deviceId);
@@ -2074,11 +2140,11 @@ Content-Length: 0\r
2074
2140
  websocketServer.emit("connection", websocket, request);
2075
2141
  });
2076
2142
  });
2077
- await new Promise((resolve4, reject) => {
2143
+ await new Promise((resolve5, reject) => {
2078
2144
  server.once("error", reject);
2079
2145
  server.listen(relayListenOptions(configuration), () => {
2080
2146
  server.off("error", reject);
2081
- resolve4();
2147
+ resolve5();
2082
2148
  });
2083
2149
  });
2084
2150
  const address = server.address();
@@ -2093,7 +2159,7 @@ Content-Length: 0\r
2093
2159
  for (const socket of websocketServer.clients) {
2094
2160
  socket.close(1001, "Relay shutting down");
2095
2161
  }
2096
- await new Promise((resolve4) => server.close(() => resolve4()));
2162
+ await new Promise((resolve5) => server.close(() => resolve5()));
2097
2163
  websocketServer.close();
2098
2164
  await runtime.dispose();
2099
2165
  };
@@ -2105,9 +2171,9 @@ var init_main = __esm(async () => {
2105
2171
  init_config();
2106
2172
  init_hub();
2107
2173
  init_store();
2108
- RequestError = class RequestError extends Schema12.TaggedErrorClass()("Relay.RequestError", {
2109
- status: Schema12.Int,
2110
- message: Schema12.String
2174
+ RequestError = class RequestError extends Schema14.TaggedErrorClass()("Relay.RequestError", {
2175
+ status: Schema14.Int,
2176
+ message: Schema14.String
2111
2177
  }) {
2112
2178
  };
2113
2179
  if (false) {}
@@ -2115,7 +2181,7 @@ var init_main = __esm(async () => {
2115
2181
 
2116
2182
  // apps/device/src/main.ts
2117
2183
  init_src();
2118
- import { Effect as Effect13 } from "effect";
2184
+ import { Effect as Effect15 } from "effect";
2119
2185
 
2120
2186
  // packages/client/src/index.ts
2121
2187
  init_src();
@@ -2129,7 +2195,7 @@ __export(exports_src, {
2129
2195
  RelayDecodeError: () => RelayDecodeError,
2130
2196
  RelayClient: () => exports_src
2131
2197
  });
2132
- import { Context, Effect, Layer, Schema as Schema2 } from "effect";
2198
+ import { Context, Effect as Effect2, Layer, Schema as Schema2 } from "effect";
2133
2199
 
2134
2200
  class RelayRequestError extends Schema2.TaggedErrorClass()("RelayClient.RequestError", {
2135
2201
  operation: Schema2.String,
@@ -2188,8 +2254,8 @@ var responseMessage = async (response, operation) => {
2188
2254
  };
2189
2255
  var make = (options) => {
2190
2256
  const baseUrl = normalizeBaseUrl(options.baseUrl);
2191
- const request = (operation, path, schema, init) => Effect.gen(function* () {
2192
- const response = yield* Effect.tryPromise({
2257
+ const request = (operation, path, schema, init) => Effect2.gen(function* () {
2258
+ const response = yield* Effect2.tryPromise({
2193
2259
  try: (signal) => fetch(`${baseUrl}${path}`, {
2194
2260
  ...init,
2195
2261
  signal: AbortSignal.any([signal, AbortSignal.timeout(30000)]),
@@ -2205,17 +2271,17 @@ var make = (options) => {
2205
2271
  })
2206
2272
  });
2207
2273
  if (!response.ok) {
2208
- return yield* Effect.fail(new RelayRequestError({
2274
+ return yield* Effect2.fail(new RelayRequestError({
2209
2275
  operation,
2210
- message: yield* Effect.promise(() => responseMessage(response, operation)),
2276
+ message: yield* Effect2.promise(() => responseMessage(response, operation)),
2211
2277
  status: response.status
2212
2278
  }));
2213
2279
  }
2214
- const json = yield* Effect.tryPromise({
2280
+ const json = yield* Effect2.tryPromise({
2215
2281
  try: () => jsonBody(response, operation),
2216
2282
  catch: (cause) => cause instanceof RelayDecodeError ? cause : new RelayDecodeError({ operation, message: String(cause) })
2217
2283
  });
2218
- return yield* Schema2.decodeUnknownEffect(schema)(json).pipe(Effect.mapError((cause) => new RelayDecodeError({ operation, message: String(cause) })));
2284
+ return yield* Schema2.decodeUnknownEffect(schema)(json).pipe(Effect2.mapError((cause) => new RelayDecodeError({ operation, message: String(cause) })));
2219
2285
  });
2220
2286
  return Service.of({
2221
2287
  devices: () => request("RelayClient.devices", "/api/devices", Schema2.Array(Device)),
@@ -2242,9 +2308,9 @@ var make = (options) => {
2242
2308
  abandonOperation: (operationId) => request("RelayClient.abandonOperation", `/api/operations/${encodeURIComponent(operationId)}/abandon`, DeviceOperation, { method: "POST" })
2243
2309
  });
2244
2310
  };
2245
- var exchangePairing = (baseUrl, input) => Effect.gen(function* () {
2311
+ var exchangePairing = (baseUrl, input) => Effect2.gen(function* () {
2246
2312
  const operation = "RelayClient.exchangePairing";
2247
- const response = yield* Effect.tryPromise({
2313
+ const response = yield* Effect2.tryPromise({
2248
2314
  try: (signal) => fetch(`${normalizeBaseUrl(baseUrl)}/api/auth/pair`, {
2249
2315
  method: "POST",
2250
2316
  signal: AbortSignal.any([signal, AbortSignal.timeout(30000)]),
@@ -2257,17 +2323,17 @@ var exchangePairing = (baseUrl, input) => Effect.gen(function* () {
2257
2323
  })
2258
2324
  });
2259
2325
  if (!response.ok) {
2260
- return yield* Effect.fail(new RelayRequestError({
2326
+ return yield* Effect2.fail(new RelayRequestError({
2261
2327
  operation,
2262
- message: yield* Effect.promise(() => responseMessage(response, operation)),
2328
+ message: yield* Effect2.promise(() => responseMessage(response, operation)),
2263
2329
  status: response.status
2264
2330
  }));
2265
2331
  }
2266
- const json = yield* Effect.tryPromise({
2332
+ const json = yield* Effect2.tryPromise({
2267
2333
  try: () => jsonBody(response, operation),
2268
2334
  catch: (cause) => cause instanceof RelayDecodeError ? cause : new RelayDecodeError({ operation, message: String(cause) })
2269
2335
  });
2270
- return yield* Schema2.decodeUnknownEffect(PairingResult)(json).pipe(Effect.mapError((cause) => new RelayDecodeError({ operation, message: String(cause) })));
2336
+ return yield* Schema2.decodeUnknownEffect(PairingResult)(json).pipe(Effect2.mapError((cause) => new RelayDecodeError({ operation, message: String(cause) })));
2271
2337
  });
2272
2338
  var layer = (options) => Layer.succeed(Service, make(options));
2273
2339
 
@@ -2276,7 +2342,7 @@ init_src();
2276
2342
 
2277
2343
  // packages/providers/src/index.ts
2278
2344
  init_src();
2279
- import { Effect as Effect2, Schema as Schema3 } from "effect";
2345
+ import { Effect as Effect3, Schema as Schema3 } from "effect";
2280
2346
  import { accessSync, constants } from "node:fs";
2281
2347
  import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
2282
2348
  import { homedir, platform, tmpdir } from "node:os";
@@ -2341,8 +2407,8 @@ var findExecutable = (command) => {
2341
2407
  }
2342
2408
  return;
2343
2409
  };
2344
- var available = (provider) => findExecutable(executables[provider]) !== undefined;
2345
- var availableProviders = () => Provider.literals.filter(available);
2410
+ var available = (provider) => provider !== "grok-bot" && findExecutable(executables[provider]) !== undefined;
2411
+ var availableProviders = () => Provider.literals.filter((provider) => provider !== "grok-bot").filter(available);
2346
2412
  var boundedText = (stream, limit, onOverflow) => new Promise((resolve, reject) => {
2347
2413
  const chunks = [];
2348
2414
  let size = 0;
@@ -2642,7 +2708,7 @@ var terminateProcessTree = async (pid) => {
2642
2708
  process.kill(-pid, "SIGKILL");
2643
2709
  await waitForProcessGroup(pid, 1000);
2644
2710
  };
2645
- var run = (options) => Effect2.tryPromise({
2711
+ var run = (options) => Effect3.tryPromise({
2646
2712
  try: async (signal) => {
2647
2713
  const executable = findExecutable(executables[options.provider]);
2648
2714
  if (executable === undefined) {
@@ -2660,7 +2726,8 @@ var run = (options) => Effect2.tryPromise({
2660
2726
  env: {
2661
2727
  ...providerEnvironment(),
2662
2728
  COHALL_PROVIDER: options.provider,
2663
- COHALL_THREAD_ID: options.threadId
2729
+ COHALL_THREAD_ID: options.threadId,
2730
+ ...options.taskId === undefined ? {} : { COHALL_TASK_ID: options.taskId }
2664
2731
  },
2665
2732
  detached: platform() !== "win32",
2666
2733
  stdio: ["pipe", "pipe", "pipe"]
@@ -2735,23 +2802,22 @@ var run = (options) => Effect2.tryPromise({
2735
2802
  });
2736
2803
 
2737
2804
  // apps/device/src/cli.ts
2738
- import { Effect as Effect7, Schema as Schema8 } from "effect";
2739
- import { access as access4, mkdir as mkdir5, readFile as readFile4, stat as stat3, writeFile as writeFile6 } from "node:fs/promises";
2805
+ 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";
2740
2807
  import { homedir as homedir4 } from "node:os";
2741
- import { dirname as dirname5, join as join6 } from "node:path";
2808
+ import { dirname as dirname6, join as join7, resolve as resolve3 } from "node:path";
2742
2809
 
2743
2810
  // skills/cohall/SKILL.md
2744
2811
  var SKILL_default = `---
2745
2812
  name: cohall
2746
- description: Delegate work to an agent on another user-owned device through the Cohall CLI. Use when the user asks to run, research, or check something on another device, names a Cohall target such as @macbook or @server, or needs machine-local state or capabilities such as a signed-in browser, Xcode, Docker, deployment access, a repository checkout, or unavailable tools. Carry the relevant current-conversation context into the handoff automatically.
2813
+ description: Delegate through Cohall to another user-owned device or an existing Grok Bot. Use when the user names a Cohall target, asks to message one of their bots, or needs another machine's tools, files, or signed-in services. Carry relevant conversation context into the handoff.
2747
2814
  ---
2748
2815
 
2749
2816
  # Cohall
2750
2817
 
2751
- Cohall sends a focused task to an agent running on another device. The target
2818
+ Cohall sends a focused task to a coding agent or an existing named Grok Bot. The target
2752
2819
  keeps its local files, credentials, browser state, provider login, skills, and
2753
- permissions. The relay carries prompts, final results, and task state; it is not
2754
- a lead agent.
2820
+ permissions. The relay carries prompts, final results, and task state.
2755
2821
 
2756
2822
  Use the installed \`cohall\` executable when it is available. Fall back to
2757
2823
  \`npx -y @akshar5/cohall\` only when Cohall is not installed globally.
@@ -2759,9 +2825,8 @@ Use the installed \`cohall\` executable when it is available. Fall back to
2759
2825
  ## Recognize cross-device requests
2760
2826
 
2761
2827
  Treat phrases such as “run this on my Mac,” “ask \`@server\`,” or “have the Linux
2762
- machine check this” as target intent. The \`@name\` form is a Cohall device
2763
- selector, not chat syntax supplied by the harness. Resolve it with \`cohall
2764
- devices\`, then delegate the smallest useful outcome.
2828
+ machine check this” as target intent. Resolve computers with \`cohall devices\`
2829
+ and named bots with \`cohall bots\`, then delegate the smallest useful outcome.
2765
2830
 
2766
2831
  Good reasons to delegate include:
2767
2832
 
@@ -2805,8 +2870,9 @@ Do not delegate ordinary local work when the other device provides no advantage.
2805
2870
  --context 'Why: deployment 184 failed after 15:00 UTC. Known: local checks passed and the provider status page reported elevated errors. Need: determine whether the outage explains our failure, with primary-source links and contrary evidence.'
2806
2871
  \`\`\`
2807
2872
 
2808
- Providers are \`codex\`, \`claude-code\`, and \`opencode\`. Omit \`--provider\` to
2809
- use Codex. Omit \`--target\` only when Cohall may choose a matching device.
2873
+ Coding providers are \`codex\`, \`claude-code\`, and \`opencode\`. Omit \`--provider\`
2874
+ to use Codex on a computer; selecting a named bot infers \`grok-bot\`.
2875
+ Omit \`--target\` only when Cohall may choose a matching computer.
2810
2876
 
2811
2877
  5. The command waits by default and returns JSON. Treat work as successful only
2812
2878
  when \`status\` is \`completed\`; use \`result\` in the current task. Report a
@@ -2822,9 +2888,43 @@ Do not delegate ordinary local work when the other device provides no advantage.
2822
2888
  --prompt 'Check whether yesterday’s deployment failed for the same reason.'
2823
2889
  \`\`\`
2824
2890
 
2825
- When an agent running as a Cohall task delegates again, \`COHALL_THREAD_ID\`
2826
- automatically carries the current thread. Pass \`--thread\` explicitly from an
2827
- ordinary shell or a separate agent turn.
2891
+ When a coding agent running as a Cohall task delegates again, \`COHALL_THREAD_ID\`
2892
+ and \`COHALL_TASK_ID\` carry its thread and parent task. Pass \`--thread\` and
2893
+ \`--parent\` explicitly from a Grok Bot using the IDs in its handoff.
2894
+
2895
+ ## Message named bots
2896
+
2897
+ 1. Run \`cohall bots\` and choose a listed target. Use its qualified target when
2898
+ names collide; discovery includes every available bot on configured hosts.
2899
+ 2. Send the prompt and relevant context:
2900
+
2901
+ \`\`\`bash
2902
+ cohall send @Research --prompt 'Find three project ideas worth exploring.'
2903
+ cohall send '@cloud/Video ideas' --prompt 'Suggest an opening for the video.'
2904
+ \`\`\`
2905
+
2906
+ Named bots use their existing Grok conversation and permissions. Omit
2907
+ \`--workspace\`. A Cohall thread records the exchange; it does not create an
2908
+ isolated Grok conversation.
2909
+
2910
+ 3. Reuse \`--thread <thread-id>\` for follow-ups. With an explicit thread and no
2911
+ target, Cohall resumes its most recent root bot task. Select a computer
2912
+ explicitly when delegating a child coding task:
2913
+
2914
+ \`\`\`bash
2915
+ cohall delegate --target @cloud --provider codex \\
2916
+ --thread <thread-id> --parent <task-id> --prompt 'Implement the selected idea.'
2917
+ \`\`\`
2918
+
2919
+ Different bots and the computer's coding agent can run concurrently. Each
2920
+ individual bot processes one Cohall request at a time.
2921
+
2922
+ When receiving a Cohall request inside a Grok Bot, finish with the \`cohall reply\`
2923
+ command supplied in the handoff. Pass the final answer through \`--message-file\`
2924
+ or \`--message -\` on stdin, then reply normally in chat. Use \`--error\` if unable
2925
+ to finish. This local receipt is what returns the result to the sending device;
2926
+ a chat message alone does not complete the Cohall task. The receipt survives
2927
+ worker restarts and works while the relay is temporarily unreachable.
2828
2928
 
2829
2929
  ## Context and safety
2830
2930
 
@@ -2860,8 +2960,10 @@ unless cancelled:
2860
2960
  cohall cancel <task-id>
2861
2961
  \`\`\`
2862
2962
 
2863
- Active cancellation is acknowledged by the target device; \`cancelling\` means
2864
- the provider process has not confirmed termination yet.
2963
+ For coding agents, active cancellation is acknowledged by the target device;
2964
+ \`cancelling\` means the provider process has not confirmed termination yet.
2965
+ Queued bot tasks can be cancelled. Stop an active bot in Grok Bot; its gateway
2966
+ cannot safely cancel an individual Cohall request.
2865
2967
 
2866
2968
  ## Read shared context
2867
2969
 
@@ -2910,12 +3012,12 @@ Return the diagnostic failure to the user rather than claiming remote work ran.
2910
3012
 
2911
3013
  // apps/device/src/config.ts
2912
3014
  init_src();
2913
- import { Effect as Effect3, Schema as Schema4 } from "effect";
3015
+ import { Effect as Effect4, Schema as Schema4 } from "effect";
2914
3016
  import { access, chmod as chmod2, mkdir, readFile, realpath, rename, writeFile as writeFile2 } from "node:fs/promises";
2915
3017
  import { homedir as homedir2, hostname, platform as platform2 } from "node:os";
2916
3018
  import { dirname, join as join2, resolve } from "node:path";
2917
3019
  var WorkspaceList = Schema4.Array(Schema4.NonEmptyString).check(Schema4.isMaxLength(64));
2918
- var ProviderList = Schema4.Array(Provider).check(Schema4.isMinLength(1)).check(Schema4.isMaxLength(3));
3020
+ var ProviderList = Schema4.Array(Provider).check(Schema4.isMinLength(1)).check(Schema4.isMaxLength(Provider.literals.length));
2919
3021
  var Sandbox = Schema4.Literals(["read-only", "workspace-write", "danger-full-access"]);
2920
3022
  var StoredConfiguration = Schema4.Struct({
2921
3023
  version: Schema4.Literal(1),
@@ -2927,12 +3029,14 @@ var StoredConfiguration = Schema4.Struct({
2927
3029
  deviceToken: Schema4.optionalKey(Schema4.NonEmptyString),
2928
3030
  providers: Schema4.optionalKey(ProviderList),
2929
3031
  model: Schema4.optionalKey(Schema4.NonEmptyString),
2930
- sandbox: Schema4.optionalKey(Sandbox)
3032
+ sandbox: Schema4.optionalKey(Sandbox),
3033
+ grokGateway: Schema4.optionalKey(Schema4.NonEmptyString)
2931
3034
  });
2932
3035
  var ClientConfiguration = Schema4.Struct({
2933
3036
  relayUrl: Schema4.NonEmptyString,
2934
3037
  token: Schema4.NonEmptyString,
2935
- mcpThreadId: Schema4.optionalKey(Schema4.String)
3038
+ mcpThreadId: Schema4.optionalKey(Schema4.String),
3039
+ mcpTaskId: Schema4.optionalKey(Schema4.String)
2936
3040
  });
2937
3041
  var DeviceConfiguration = Schema4.Struct({
2938
3042
  relayUrl: Schema4.NonEmptyString,
@@ -2942,7 +3046,8 @@ var DeviceConfiguration = Schema4.Struct({
2942
3046
  workspaces: WorkspaceList,
2943
3047
  providers: Schema4.optionalKey(ProviderList),
2944
3048
  model: Schema4.optionalKey(Schema4.NonEmptyString),
2945
- sandbox: Schema4.optionalKey(Sandbox)
3049
+ sandbox: Schema4.optionalKey(Sandbox),
3050
+ grokGateway: Schema4.optionalKey(Schema4.NonEmptyString)
2946
3051
  });
2947
3052
  var defaultConfigDirectory = () => {
2948
3053
  if (platform2() === "win32") {
@@ -2978,7 +3083,7 @@ var normalizeRelayUrl = (input) => {
2978
3083
  url.pathname = "/";
2979
3084
  return url.toString().replace(/\/$/, "");
2980
3085
  };
2981
- var parseWorkspaces = (workspaceList, workspaceJson) => Effect3.tryPromise({
3086
+ var parseWorkspaces = (workspaceList, workspaceJson) => Effect4.tryPromise({
2982
3087
  try: async () => {
2983
3088
  const values = workspaceJson === undefined ? workspaceList.split(",").map((workspace) => workspace.trim()).filter((workspace) => workspace.length > 0) : Schema4.decodeUnknownSync(WorkspaceList)(JSON.parse(workspaceJson));
2984
3089
  const canonical = await Promise.all(values.map((workspace) => realpath(resolve(workspace))));
@@ -3031,7 +3136,7 @@ var makeStoredConfiguration = async (input) => {
3031
3136
  const existing = await readStoredConfiguration();
3032
3137
  const relayUrl = normalizeRelayUrl(input.relayUrl);
3033
3138
  const retainedCredentials = credentialsForRelay(existing, relayUrl);
3034
- const canonicalWorkspaces = input.workspaces === undefined ? existing?.workspaces ?? [] : await Effect3.runPromise(parseWorkspaces("", JSON.stringify(input.workspaces)));
3139
+ const canonicalWorkspaces = input.workspaces === undefined ? existing?.workspaces ?? [] : await Effect4.runPromise(parseWorkspaces("", JSON.stringify(input.workspaces)));
3035
3140
  const clientToken = input.clientToken ?? retainedCredentials.clientToken;
3036
3141
  const deviceToken = input.deviceToken ?? retainedCredentials.deviceToken;
3037
3142
  const providers = input.providers === "auto" ? undefined : input.providers ?? existing?.providers;
@@ -3045,7 +3150,8 @@ var makeStoredConfiguration = async (input) => {
3045
3150
  ...deviceToken === undefined ? {} : { deviceToken },
3046
3151
  ...providers === undefined ? {} : { providers },
3047
3152
  ...existing?.model === undefined ? {} : { model: existing.model },
3048
- ...existing?.sandbox === undefined ? {} : { sandbox: existing.sandbox }
3153
+ ...existing?.sandbox === undefined ? {} : { sandbox: existing.sandbox },
3154
+ ...existing?.grokGateway === undefined ? {} : { grokGateway: existing.grokGateway }
3049
3155
  });
3050
3156
  };
3051
3157
  var environmentWorkspaces = async (stored) => {
@@ -3054,7 +3160,7 @@ var environmentWorkspaces = async (stored) => {
3054
3160
  if (list === undefined && json === undefined) {
3055
3161
  return stored?.workspaces ?? [];
3056
3162
  }
3057
- return Effect3.runPromise(parseWorkspaces(list ?? "", json));
3163
+ return Effect4.runPromise(parseWorkspaces(list ?? "", json));
3058
3164
  };
3059
3165
  var environmentProvider = () => process.env.COHALL_MODEL ?? process.env.COHALL_CODEX_MODEL;
3060
3166
  var environmentSandbox = () => {
@@ -3075,7 +3181,7 @@ var storedOrDefaults = async () => {
3075
3181
  workspaces: []
3076
3182
  });
3077
3183
  };
3078
- var loadClientConfiguration = Effect3.tryPromise({
3184
+ var loadClientConfiguration = Effect4.tryPromise({
3079
3185
  try: async () => {
3080
3186
  const stored = await storedOrDefaults();
3081
3187
  const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl);
@@ -3086,12 +3192,13 @@ var loadClientConfiguration = Effect3.tryPromise({
3086
3192
  return ClientConfiguration.make({
3087
3193
  relayUrl,
3088
3194
  token,
3089
- ...process.env.COHALL_THREAD_ID === undefined ? {} : { mcpThreadId: process.env.COHALL_THREAD_ID }
3195
+ ...process.env.COHALL_THREAD_ID === undefined ? {} : { mcpThreadId: process.env.COHALL_THREAD_ID },
3196
+ ...process.env.COHALL_TASK_ID === undefined ? {} : { mcpTaskId: process.env.COHALL_TASK_ID }
3090
3197
  });
3091
3198
  },
3092
3199
  catch: (cause) => cause instanceof Error ? cause : new Error(String(cause))
3093
3200
  });
3094
- var loadOwnerConfiguration = Effect3.tryPromise({
3201
+ var loadOwnerConfiguration = Effect4.tryPromise({
3095
3202
  try: async () => {
3096
3203
  const stored = await storedOrDefaults();
3097
3204
  const ownerTokenPath = join2(relayDataDirectory(), "owner-token");
@@ -3111,7 +3218,7 @@ var loadOwnerConfiguration = Effect3.tryPromise({
3111
3218
  },
3112
3219
  catch: (cause) => cause instanceof Error ? cause : new Error(String(cause))
3113
3220
  });
3114
- var loadDeviceConfiguration = Effect3.tryPromise({
3221
+ var loadDeviceConfiguration = Effect4.tryPromise({
3115
3222
  try: async () => {
3116
3223
  const stored = await storedOrDefaults();
3117
3224
  const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl);
@@ -3126,6 +3233,7 @@ var loadDeviceConfiguration = Effect3.tryPromise({
3126
3233
  const model = environmentProvider() ?? stored.model;
3127
3234
  const sandbox = environmentSandbox() ?? stored.sandbox;
3128
3235
  const providers = environmentProviders(stored);
3236
+ const grokGateway = process.env.COHALL_GROK_GATEWAY ?? stored.grokGateway;
3129
3237
  return DeviceConfiguration.make({
3130
3238
  relayUrl,
3131
3239
  token,
@@ -3134,7 +3242,8 @@ var loadDeviceConfiguration = Effect3.tryPromise({
3134
3242
  workspaces,
3135
3243
  ...providers === undefined ? {} : { providers },
3136
3244
  ...model === undefined ? {} : { model },
3137
- ...sandbox === undefined ? {} : { sandbox }
3245
+ ...sandbox === undefined ? {} : { sandbox },
3246
+ ...grokGateway === undefined ? {} : { grokGateway: resolve(grokGateway) }
3138
3247
  });
3139
3248
  },
3140
3249
  catch: (cause) => cause instanceof Error ? cause : new Error(String(cause))
@@ -3143,7 +3252,7 @@ var knownProviders = Provider.literals;
3143
3252
 
3144
3253
  // apps/device/src/delegation.ts
3145
3254
  init_src();
3146
- import { Effect as Effect4, Schedule, Schema as Schema5 } from "effect";
3255
+ import { Effect as Effect5, Schedule, Schema as Schema5 } from "effect";
3147
3256
 
3148
3257
  class DeviceSelectionError extends Schema5.TaggedErrorClass()("Cohall.DeviceSelectionError", { message: Schema5.String, devices: Schema5.Array(Schema5.String) }) {
3149
3258
  }
@@ -3156,50 +3265,112 @@ var TaskResult = Schema5.Struct({
3156
3265
  status: TaskStatus,
3157
3266
  provider: Provider,
3158
3267
  target_device_id: DeviceId,
3268
+ bot_id: Schema5.optionalKey(BotId),
3159
3269
  result: Schema5.optionalKey(Schema5.String),
3160
3270
  error: Schema5.optionalKey(Schema5.String)
3161
3271
  });
3162
3272
  var matchingDevices = (devices, target) => {
3163
- const normalized = target.replace(/^@/, "").toLowerCase();
3164
- const byId = devices.find((device) => device.id === target);
3273
+ const value = target.replace(/^@/, "");
3274
+ const normalized = value.toLowerCase();
3275
+ const byId = devices.find((device) => device.id === value);
3165
3276
  return byId === undefined ? devices.filter((device) => device.name.toLowerCase() === normalized || device.hostname.toLowerCase() === normalized) : [byId];
3166
3277
  };
3278
+ var listBots = (devices) => devices.flatMap((device) => (device.bots ?? []).map((bot) => ({
3279
+ id: bot.id,
3280
+ name: bot.name,
3281
+ ...bot.description === undefined ? {} : { description: bot.description },
3282
+ device_id: device.id,
3283
+ device_name: device.name,
3284
+ status: device.status,
3285
+ target: `@${device.id}/${bot.id}`
3286
+ })));
3287
+ var selectTarget = Effect5.fn("Cohall.selectTarget")(function* (devices, target) {
3288
+ const value = target.replace(/^@/, "");
3289
+ const separator = value.indexOf("/");
3290
+ const qualified = separator !== -1;
3291
+ const deviceMatches = matchingDevices(devices, qualified ? value.slice(0, separator) : value);
3292
+ const botValue = qualified ? value.slice(separator + 1) : value;
3293
+ const bots = listBots(qualified ? deviceMatches : devices);
3294
+ const botsById = bots.filter((bot) => bot.id === botValue);
3295
+ const botMatches = botsById.length > 0 ? botsById : bots.filter((bot) => bot.name.toLowerCase() === botValue.toLowerCase());
3296
+ const exactDevice = qualified ? undefined : deviceMatches.find((device) => device.id === value);
3297
+ if (exactDevice !== undefined)
3298
+ return { deviceId: exactDevice.id, botId: undefined };
3299
+ const matches = [
3300
+ ...qualified ? [] : deviceMatches.map((device) => ({ deviceId: device.id, botId: undefined })),
3301
+ ...botMatches.map((bot) => ({ deviceId: bot.device_id, botId: bot.id }))
3302
+ ];
3303
+ const selected = matches[0];
3304
+ if (matches.length === 1 && selected !== undefined)
3305
+ return selected;
3306
+ return yield* new DeviceSelectionError({
3307
+ message: matches.length === 0 ? `No Cohall device or bot matches ${target}; use cohall devices or cohall bots` : `${target} is ambiguous; use a device ID or @device-id/bot-id from cohall bots`,
3308
+ devices: [
3309
+ ...deviceMatches.map((device) => `${device.name} (@${device.id})`),
3310
+ ...botMatches.map((bot) => `${bot.name} on ${bot.device_name} (${bot.target})`)
3311
+ ]
3312
+ });
3313
+ });
3167
3314
  var taskResult = (task) => TaskResult.make({
3168
3315
  task_id: task.id,
3169
3316
  thread_id: task.threadId,
3170
3317
  status: task.status,
3171
3318
  provider: task.provider,
3172
3319
  target_device_id: task.targetDeviceId,
3320
+ ...task.botId === undefined ? {} : { bot_id: task.botId },
3173
3321
  ...task.result === undefined ? {} : { result: task.result },
3174
3322
  ...task.error === undefined ? {} : { error: task.error }
3175
3323
  });
3176
- var createDelegation = Effect4.fn("Cohall.createDelegation")(function* (client, configuration, options) {
3324
+ var createDelegation = Effect5.fn("Cohall.createDelegation")(function* (client, configuration, options) {
3177
3325
  const devices = yield* client.devices();
3178
- const matches = options.target === undefined ? [] : matchingDevices(devices, options.target);
3179
- if (options.target !== undefined && matches.length !== 1) {
3326
+ if (devices.length === 0) {
3180
3327
  return yield* new DeviceSelectionError({
3181
- message: matches.length === 0 ? `No Cohall device matches ${options.target}` : `${options.target} is ambiguous; use a device ID`,
3182
- devices: matches.length === 0 ? devices.map((device) => `${device.name} (${device.id})`) : matches.map((device) => `${device.name} (${device.id})`)
3328
+ message: "No Cohall devices are registered",
3329
+ devices: []
3183
3330
  });
3184
3331
  }
3185
- if (devices.length === 0) {
3332
+ let target = options.target === undefined ? undefined : yield* selectTarget(devices, options.target);
3333
+ if (target === undefined && options.threadId !== undefined && options.provider === undefined && options.parentTaskId === undefined && configuration.mcpTaskId === undefined) {
3334
+ const context = yield* client.threadContext(options.threadId);
3335
+ const previous = context.tasks.findLast((task) => task.parentTaskId === undefined);
3336
+ if (previous === undefined && context.truncated) {
3337
+ return yield* new DeviceSelectionError({
3338
+ message: "Earlier thread targets are no longer in the retained context; specify --target",
3339
+ devices: []
3340
+ });
3341
+ }
3342
+ if (previous?.provider === "grok-bot" && previous.botId !== undefined) {
3343
+ target = yield* selectTarget(devices, `@${previous.targetDeviceId}/${previous.botId}`);
3344
+ }
3345
+ }
3346
+ const botId = target?.botId;
3347
+ if (botId !== undefined && options.provider !== undefined && options.provider !== "grok-bot") {
3186
3348
  return yield* new DeviceSelectionError({
3187
- message: "No Cohall devices are registered",
3349
+ message: `A bot target requires provider grok-bot, not ${options.provider}`,
3188
3350
  devices: []
3189
3351
  });
3190
3352
  }
3191
- const target = matches[0];
3353
+ if (botId === undefined && options.provider === "grok-bot") {
3354
+ return yield* new DeviceSelectionError({
3355
+ message: "Select a bot with --target; use cohall bots to list available bots",
3356
+ devices: []
3357
+ });
3358
+ }
3359
+ const provider = botId === undefined ? options.provider : "grok-bot";
3192
3360
  const inheritedThread = configuration.mcpThreadId;
3361
+ const parentTaskId = options.parentTaskId ?? (configuration.mcpTaskId === undefined ? undefined : yield* Schema5.decodeUnknownEffect(TaskId)(configuration.mcpTaskId));
3193
3362
  return yield* client.createTask({
3194
3363
  prompt: options.prompt,
3195
- ...target === undefined ? {} : { targetDeviceId: target.id },
3364
+ ...target === undefined ? {} : { targetDeviceId: target.deviceId },
3365
+ ...botId === undefined ? {} : { botId },
3196
3366
  ...options.threadId !== undefined ? { threadId: options.threadId } : inheritedThread === undefined ? {} : { threadId: ThreadId.make(inheritedThread) },
3197
3367
  ...options.context === undefined ? {} : { context: options.context },
3198
3368
  ...options.workspace === undefined ? {} : { workspace: options.workspace },
3199
- ...options.provider === undefined ? {} : { provider: options.provider }
3369
+ ...provider === undefined ? {} : { provider },
3370
+ ...parentTaskId === undefined ? {} : { parentTaskId }
3200
3371
  });
3201
3372
  });
3202
- var waitForTask = Effect4.fn("Cohall.waitForTask")(function* (client, initial, timeoutSeconds) {
3373
+ var waitForTask = Effect5.fn("Cohall.waitForTask")(function* (client, initial, timeoutSeconds) {
3203
3374
  const deadline = Date.now() + timeoutSeconds * 1000;
3204
3375
  let task = initial;
3205
3376
  while (!isTerminalTask(task)) {
@@ -3210,28 +3381,28 @@ var waitForTask = Effect4.fn("Cohall.waitForTask")(function* (client, initial, t
3210
3381
  status: task.status
3211
3382
  });
3212
3383
  }
3213
- yield* Effect4.sleep("1 second");
3384
+ yield* Effect5.sleep("1 second");
3214
3385
  task = yield* client.getTask(task.id);
3215
3386
  }
3216
3387
  return task;
3217
3388
  });
3218
- var followTaskTrace = Effect4.fn("Cohall.followTaskTrace")(function* (client, taskId, onTrace) {
3389
+ var followTaskTrace = Effect5.fn("Cohall.followTaskTrace")(function* (client, taskId, onTrace) {
3219
3390
  let revision;
3220
- const poll = client.traceTask(taskId).pipe(Effect4.tap((trace) => {
3391
+ const poll = client.traceTask(taskId).pipe(Effect5.tap((trace) => {
3221
3392
  const latest = trace.events.at(-1);
3222
3393
  const nextRevision = `${trace.status}:${latest?.kind ?? "none"}:${latest?.at ?? "none"}`;
3223
3394
  if (nextRevision === revision) {
3224
- return Effect4.succeed(undefined);
3395
+ return Effect5.succeed(undefined);
3225
3396
  }
3226
3397
  revision = nextRevision;
3227
- return Effect4.sync(() => onTrace(trace));
3398
+ return Effect5.sync(() => onTrace(trace));
3228
3399
  }));
3229
- return yield* poll.pipe(Effect4.repeat({
3400
+ return yield* poll.pipe(Effect5.repeat({
3230
3401
  until: (trace) => terminalTaskStatuses.has(trace.status),
3231
3402
  schedule: Schedule.spaced("1 second")
3232
3403
  }));
3233
3404
  });
3234
- var threadContext = Effect4.fn("Cohall.threadContext")(function* (client, threadId) {
3405
+ var threadContext = Effect5.fn("Cohall.threadContext")(function* (client, threadId) {
3235
3406
  return yield* client.threadContext(threadId);
3236
3407
  });
3237
3408
 
@@ -3280,8 +3451,448 @@ var allDeviceHealth = (devices, localVersion) => {
3280
3451
  };
3281
3452
  };
3282
3453
 
3454
+ // apps/device/src/grok-bot.ts
3455
+ init_src();
3456
+ import { Effect as Effect6, Schedule as Schedule2, Schema as Schema7 } from "effect";
3457
+ import { open as open2 } from "node:fs/promises";
3458
+ import { isIP } from "node:net";
3459
+
3460
+ // apps/device/src/bot-replies.ts
3461
+ init_src();
3462
+ import { Schema as Schema6 } from "effect";
3463
+ import { randomUUID } from "node:crypto";
3464
+ import { chmod as chmod3, link, mkdir as mkdir2, open, unlink } from "node:fs/promises";
3465
+ import { dirname as dirname2, join as join3 } from "node:path";
3466
+ import { platform as platform3 } from "node:os";
3467
+ var replyLifetimeMs = 6 * 60 * 60 * 1000;
3468
+ var Pending = Schema6.Struct({
3469
+ taskId: TaskId,
3470
+ botId: BotId,
3471
+ threadId: ThreadId,
3472
+ deadline: Schema6.Int.check(Schema6.isGreaterThanOrEqualTo(0))
3473
+ });
3474
+ var Dispatch = Schema6.Struct({ taskId: TaskId });
3475
+ var Reply = Schema6.Union([
3476
+ Schema6.Struct({
3477
+ result: Schema6.String.check(Schema6.isMaxLength(131072)),
3478
+ error: Schema6.optionalKey(Schema6.Never)
3479
+ }),
3480
+ Schema6.Struct({
3481
+ error: Schema6.NonEmptyString.check(Schema6.isMaxLength(16384)),
3482
+ result: Schema6.optionalKey(Schema6.Never)
3483
+ })
3484
+ ]);
3485
+ var Receipt = Schema6.Struct({
3486
+ taskId: TaskId,
3487
+ submittedAt: Schema6.Int.check(Schema6.isGreaterThanOrEqualTo(0)),
3488
+ reply: Reply
3489
+ });
3490
+ var isMissing = (cause) => cause instanceof Error && ("code" in cause) && cause.code === "ENOENT";
3491
+ var alreadyExists = (cause) => cause instanceof Error && ("code" in cause) && cause.code === "EEXIST";
3492
+ var paths = (taskId) => {
3493
+ const id = Schema6.decodeUnknownSync(TaskId)(taskId);
3494
+ const directory = join3(dirname2(configurationPath()), "bot-replies");
3495
+ return {
3496
+ directory,
3497
+ pending: join3(directory, `${id}.pending.json`),
3498
+ receipt: join3(directory, `${id}.reply.json`),
3499
+ dispatch: join3(directory, `${id}.dispatch.json`)
3500
+ };
3501
+ };
3502
+ var readJson = async (path, limit) => {
3503
+ const file = await open(path, "r").catch((cause) => {
3504
+ if (isMissing(cause))
3505
+ return;
3506
+ throw cause;
3507
+ });
3508
+ if (file === undefined)
3509
+ return;
3510
+ try {
3511
+ const buffer = Buffer.alloc(limit + 1);
3512
+ let size = 0;
3513
+ while (size < buffer.length) {
3514
+ const { bytesRead } = await file.read(buffer, size, buffer.length - size, size);
3515
+ if (bytesRead === 0)
3516
+ break;
3517
+ size += bytesRead;
3518
+ }
3519
+ if (size > limit)
3520
+ throw new Error("Bot reply record exceeds its size limit");
3521
+ return JSON.parse(buffer.subarray(0, size).toString("utf8"));
3522
+ } catch {
3523
+ throw new Error("Invalid local bot reply record");
3524
+ } finally {
3525
+ await file.close();
3526
+ }
3527
+ };
3528
+ var pendingReply = async (taskId) => {
3529
+ const value = await readJson(paths(taskId).pending, 4096);
3530
+ if (value === undefined)
3531
+ return;
3532
+ const decoded = Schema6.decodeUnknownResult(Pending)(value);
3533
+ if (decoded._tag === "Failure" || decoded.success.taskId !== taskId) {
3534
+ throw new Error("Invalid local bot reply manifest");
3535
+ }
3536
+ return decoded.success;
3537
+ };
3538
+ var publish = async (path, value) => {
3539
+ const temporary = `${path}.${randomUUID()}.tmp`;
3540
+ const file = await open(temporary, "wx", 384);
3541
+ try {
3542
+ try {
3543
+ await file.writeFile(`${JSON.stringify(value)}
3544
+ `, "utf8");
3545
+ await file.sync();
3546
+ } finally {
3547
+ await file.close();
3548
+ }
3549
+ await link(temporary, path);
3550
+ if (platform3() !== "win32") {
3551
+ const directory = await open(dirname2(path), "r");
3552
+ try {
3553
+ await directory.sync();
3554
+ } finally {
3555
+ await directory.close();
3556
+ }
3557
+ }
3558
+ return true;
3559
+ } catch (cause) {
3560
+ if (alreadyExists(cause))
3561
+ return false;
3562
+ throw cause;
3563
+ } finally {
3564
+ await unlink(temporary);
3565
+ }
3566
+ };
3567
+ var shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
3568
+ var prepareBotReply = async (task) => {
3569
+ if (task.provider !== "grok-bot" || task.botId === undefined) {
3570
+ throw new Error("Only a named Grok Bot task can receive a bot reply");
3571
+ }
3572
+ const files = paths(task.id);
3573
+ await mkdir2(files.directory, { recursive: true, mode: 448 });
3574
+ await chmod3(files.directory, 448);
3575
+ const record2 = Pending.make({
3576
+ taskId: task.id,
3577
+ botId: task.botId,
3578
+ threadId: task.threadId,
3579
+ deadline: Date.now() + replyLifetimeMs
3580
+ });
3581
+ await publish(files.pending, record2);
3582
+ const pending = await pendingReply(task.id);
3583
+ if (pending === undefined || pending.botId !== task.botId || pending.threadId !== task.threadId) {
3584
+ throw new Error("Bot reply manifest does not match this task");
3585
+ }
3586
+ const dispatch = await readJson(files.dispatch, 4096);
3587
+ if (dispatch !== undefined) {
3588
+ const decoded = Schema6.decodeUnknownResult(Dispatch)(dispatch);
3589
+ if (decoded._tag === "Failure" || decoded.success.taskId !== task.id) {
3590
+ throw new Error("Invalid local bot dispatch record");
3591
+ }
3592
+ }
3593
+ return {
3594
+ deadline: pending.deadline,
3595
+ command: `COHALL_CONFIG=${shellQuote(configurationPath())} cohall`,
3596
+ dispatched: dispatch !== undefined
3597
+ };
3598
+ };
3599
+ var claimBotDispatch = async (taskId) => {
3600
+ const pending = await pendingReply(taskId);
3601
+ if (pending === undefined)
3602
+ throw new Error("No pending bot reply for this task");
3603
+ if (Date.now() >= pending.deadline)
3604
+ throw new Error("Bot reply deadline has expired");
3605
+ return publish(paths(taskId).dispatch, { taskId });
3606
+ };
3607
+ var readBotReply = async (taskId) => {
3608
+ const pending = await pendingReply(taskId);
3609
+ if (pending === undefined)
3610
+ return;
3611
+ const value = await readJson(paths(taskId).receipt, 1024 * 1024);
3612
+ if (value === undefined)
3613
+ return;
3614
+ const decoded = Schema6.decodeUnknownResult(Receipt)(value);
3615
+ if (decoded._tag === "Failure" || decoded.success.taskId !== taskId) {
3616
+ throw new Error("Invalid local bot reply receipt");
3617
+ }
3618
+ if (decoded.success.submittedAt >= pending.deadline) {
3619
+ throw new Error("Bot reply was submitted after its deadline");
3620
+ }
3621
+ return decoded.success.reply;
3622
+ };
3623
+ var writeBotReply = async (taskId, reply) => {
3624
+ const decoded = Schema6.decodeUnknownResult(Reply)(reply);
3625
+ if (decoded._tag === "Failure") {
3626
+ throw new Error("Provide a result of at most 131072 characters or a nonempty error of at most 16384 characters");
3627
+ }
3628
+ const pending = await pendingReply(taskId);
3629
+ if (pending === undefined)
3630
+ throw new Error("No pending bot reply for this task");
3631
+ const existing = await readBotReply(taskId);
3632
+ if (existing !== undefined) {
3633
+ if (JSON.stringify(existing) === JSON.stringify(decoded.success))
3634
+ return;
3635
+ throw new Error("A different reply has already been submitted for this task");
3636
+ }
3637
+ const submittedAt = Date.now();
3638
+ if (submittedAt >= pending.deadline)
3639
+ throw new Error("Bot reply deadline has expired");
3640
+ if (await publish(paths(taskId).receipt, { taskId, submittedAt, reply: decoded.success }))
3641
+ return;
3642
+ const winner = await readBotReply(taskId);
3643
+ if (JSON.stringify(winner) !== JSON.stringify(decoded.success)) {
3644
+ throw new Error("A different reply has already been submitted for this task");
3645
+ }
3646
+ };
3647
+ var cleanupBotReply = async (taskId) => {
3648
+ const files = paths(taskId);
3649
+ for (const path of [files.pending, files.receipt, files.dispatch]) {
3650
+ await unlink(path).catch((cause) => {
3651
+ if (!isMissing(cause))
3652
+ throw cause;
3653
+ });
3654
+ }
3655
+ };
3656
+
3657
+ // apps/device/src/grok-bot.ts
3658
+ var maxResponseBytes = 4 * 1024 * 1024;
3659
+ var maxDiscoveryBytes = 64 * 1024;
3660
+ var text2 = Schema7.NonEmptyString;
3661
+ var Discovery = Schema7.Struct({
3662
+ port: Schema7.Int.check(Schema7.isBetween({ minimum: 1, maximum: 65535 })),
3663
+ host: Schema7.optionalKey(text2),
3664
+ scheme: Schema7.optionalKey(Schema7.Literals(["http", "https"])),
3665
+ token: Schema7.optionalKey(Schema7.String.check(Schema7.isMaxLength(16384)))
3666
+ });
3667
+ var Agent = Schema7.Struct({
3668
+ id: Bot.fields.id,
3669
+ name: Bot.fields.name,
3670
+ description: Schema7.optionalKey(Schema7.String),
3671
+ isGroup: Schema7.optionalKey(Schema7.Boolean)
3672
+ });
3673
+ var Roster = Schema7.Array(Agent).check(Schema7.isMaxLength(1024));
3674
+ var Acceptance = Schema7.Union([
3675
+ Schema7.Struct({ outcome: Schema7.Literals(["not-found", "unknown-durability"]) }),
3676
+ Schema7.Struct({
3677
+ outcome: Schema7.Literal("found"),
3678
+ record: Schema7.Struct({
3679
+ status: Schema7.Literals(["accepted", "pending", "rejected"])
3680
+ })
3681
+ })
3682
+ ]);
3683
+ var SendResult = Schema7.Struct({ accepted: Schema7.Literal(true) });
3684
+
3685
+ class GatewayError extends Error {
3686
+ }
3687
+ var decode = (schema, value, label) => {
3688
+ const result = Schema7.decodeUnknownResult(schema)(value);
3689
+ if (result._tag === "Failure")
3690
+ throw new GatewayError(`Grok Bot returned an invalid ${label}`);
3691
+ return result.success;
3692
+ };
3693
+ var readConnection = async (path) => {
3694
+ const value = await (async () => {
3695
+ const file = await open2(path, "r");
3696
+ try {
3697
+ const buffer = Buffer.alloc(maxDiscoveryBytes + 1);
3698
+ const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
3699
+ if (bytesRead > maxDiscoveryBytes)
3700
+ throw new Error("Discovery file exceeds its size limit");
3701
+ return decode(Discovery, JSON.parse(buffer.subarray(0, bytesRead).toString("utf8")), "discovery file");
3702
+ } finally {
3703
+ await file.close();
3704
+ }
3705
+ })().catch(() => {
3706
+ throw new Error("Cannot read Grok Bot gateway discovery; check its configured file and running gateway");
3707
+ });
3708
+ const host = value.host ?? "127.0.0.1";
3709
+ const normalized = host === "0.0.0.0" || host === "localhost" ? "127.0.0.1" : host === "::" || host === "::1" || host === "[::1]" ? "[::1]" : host;
3710
+ if (!(normalized === "[::1]" || isIP(normalized) === 4 && normalized.startsWith("127."))) {
3711
+ throw new Error("Grok Bot gateway must use a loopback address on this device");
3712
+ }
3713
+ return { url: `${value.scheme ?? "http"}://${normalized}:${value.port}`, token: value.token };
3714
+ };
3715
+ var rpc = async (connection, method, args, schema, signal) => {
3716
+ const controller = new AbortController;
3717
+ const deadline = setTimeout(() => controller.abort(), 20000);
3718
+ const combined = signal === undefined ? controller.signal : AbortSignal.any([signal, controller.signal]);
3719
+ try {
3720
+ const response = await fetch(`${connection.url}/api/${method}`, {
3721
+ method: "POST",
3722
+ redirect: "error",
3723
+ headers: {
3724
+ "content-type": "application/json",
3725
+ "x-sand-slim-avatars": "1",
3726
+ ...connection.token === undefined || connection.token.length === 0 ? {} : { authorization: `Bearer ${connection.token}` }
3727
+ },
3728
+ body: JSON.stringify(args),
3729
+ signal: combined
3730
+ });
3731
+ if (!response.ok) {
3732
+ await response.body?.cancel();
3733
+ throw new GatewayError(`Grok Bot ${method} failed (HTTP ${response.status})`);
3734
+ }
3735
+ if (response.body === null)
3736
+ throw new GatewayError("Grok Bot returned an empty response");
3737
+ const reader = response.body.getReader();
3738
+ const chunks = [];
3739
+ let size = 0;
3740
+ try {
3741
+ for (;; ) {
3742
+ const chunk = await reader.read();
3743
+ if (chunk.done)
3744
+ break;
3745
+ size += chunk.value.byteLength;
3746
+ if (size > maxResponseBytes)
3747
+ throw new GatewayError("Grok Bot response exceeded 4 MiB");
3748
+ chunks.push(chunk.value);
3749
+ }
3750
+ } finally {
3751
+ await reader.cancel().catch(() => {
3752
+ return;
3753
+ });
3754
+ reader.releaseLock();
3755
+ }
3756
+ let json;
3757
+ try {
3758
+ json = JSON.parse(Buffer.concat(chunks, size).toString("utf8"));
3759
+ } catch {
3760
+ throw new GatewayError(`Grok Bot ${method} returned invalid JSON`);
3761
+ }
3762
+ return decode(schema, json, `${method} response`);
3763
+ } catch (cause) {
3764
+ if (signal?.aborted)
3765
+ throw new Error("Stopped waiting for Grok Bot; its run may still be active");
3766
+ if (cause instanceof GatewayError)
3767
+ throw cause;
3768
+ throw new Error(`Grok Bot ${method} failed; check gateway availability and compatibility`);
3769
+ } finally {
3770
+ clearTimeout(deadline);
3771
+ }
3772
+ };
3773
+ var discoverGrokBots = async (path) => {
3774
+ const connection = await readConnection(path);
3775
+ const roster = await rpc(connection, "listAgents", {}, Roster);
3776
+ const bots = roster.filter((agent) => agent.isGroup !== true);
3777
+ if (bots.length > 256)
3778
+ throw new Error("Grok Bot discovery exceeds Cohall's limit of 256 bots");
3779
+ if (new Set(bots.map((bot) => bot.id)).size !== bots.length) {
3780
+ throw new Error("Grok Bot returned duplicate bot identities");
3781
+ }
3782
+ return bots.map(({ id, name, description }) => ({
3783
+ id,
3784
+ name,
3785
+ ...description === undefined ? {} : { description: description.slice(0, 512) }
3786
+ }));
3787
+ };
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
+ };
3797
+ var timeoutMessage = "Timed out waiting for the bot to reply through Cohall; the bot may still be running.";
3798
+ var requireTime = (deadline) => {
3799
+ if (Date.now() >= deadline)
3800
+ throw new Error(timeoutMessage);
3801
+ };
3802
+ var finishReply = (reply) => {
3803
+ if (reply.error !== undefined)
3804
+ throw new Error(reply.error);
3805
+ if (reply.result === undefined)
3806
+ throw new Error("Grok Bot returned an invalid Cohall reply");
3807
+ return { result: reply.result };
3808
+ };
3809
+ var taskPrompt = (task, command) => [
3810
+ task.prompt,
3811
+ task.context === undefined ? "" : `Context:
3812
+ ${task.context}`,
3813
+ `This request came through Cohall. Thread: ${task.threadId}. Task: ${task.id}.`,
3814
+ `You may use your own tools and delegate work to another Cohall device when the request calls for it. Discover devices with: ${command} devices`,
3815
+ `Delegation example: ${command} delegate --thread ${task.threadId} --parent ${task.id} --target @device --provider codex --prompt 'Concrete task' --context 'Relevant context'`,
3816
+ "When finished, hand the final answer back to Cohall so it reaches the requesting device. Write it to a UTF-8 file on this computer, then run:",
3817
+ `${command} reply ${task.id} --message-file /absolute/path/to/your-answer.txt`,
3818
+ `Alternatively pipe your answer into: ${command} reply ${task.id} --message -`,
3819
+ `If you cannot complete this task, report the reason with: ${command} reply ${task.id} --error 'Reason'`,
3820
+ "The reply command must succeed. Then send the same answer in this chat. A normal chat message alone does not complete the Cohall task."
3821
+ ].filter(Boolean).join(`
3822
+
3823
+ `);
3824
+ var runGrokBot = async (path, task, signal) => {
3825
+ const existing = await readBotReply(task.id);
3826
+ if (existing !== undefined)
3827
+ return finishReply(existing);
3828
+ const { deadline, command, dispatched } = await prepareBotReply(task);
3829
+ 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
+ const agentId = task.botId;
3842
+ if (agentId === undefined)
3843
+ throw new Error("Select a Grok Bot for this task");
3844
+ const connection = await readConnection(path);
3845
+ const lookup = () => rpc(connection, "promptAcceptanceStatus", { accountSlot: "host", agentId, clientNonce: task.id }, Acceptance, signal);
3846
+ let acceptance = await lookup();
3847
+ if (acceptance.outcome === "unknown-durability")
3848
+ throw new Error("Grok Bot cannot confirm whether this task was accepted; check its conversation before retrying");
3849
+ if (acceptance.outcome === "not-found") {
3850
+ const receipt = await readBotReply(task.id);
3851
+ if (receipt !== undefined)
3852
+ return finishReply(receipt);
3853
+ requireTime(deadline);
3854
+ const roster = await rpc(connection, "listAgents", {}, Roster, signal);
3855
+ if (!roster.some((row) => row.id === agentId && row.isGroup !== true)) {
3856
+ throw new Error("The selected Grok Bot no longer exists on this device");
3857
+ }
3858
+ requireTime(deadline);
3859
+ acceptance = await lookup();
3860
+ if (acceptance.outcome === "not-found") {
3861
+ requireTime(deadline);
3862
+ if (!await claimBotDispatch(task.id)) {
3863
+ return waitForReply();
3864
+ }
3865
+ try {
3866
+ await rpc(connection, "sendPrompt", {
3867
+ agentId,
3868
+ prompt: taskPrompt(task, command),
3869
+ clientNonce: task.id,
3870
+ directAddressedAcceptance: true
3871
+ }, SendResult, signal);
3872
+ } catch {
3873
+ if (signal.aborted)
3874
+ throw new Error("Stopped waiting for Grok Bot; its run may still be active");
3875
+ const receipt2 = await readBotReply(task.id);
3876
+ if (receipt2 !== undefined)
3877
+ 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();
3884
+ }
3885
+ }
3886
+ }
3887
+ if (acceptance.outcome === "unknown-durability")
3888
+ throw new Error("Grok Bot cannot confirm task acceptance; the prompt was not resent");
3889
+ if (acceptance.outcome === "found" && acceptance.record.status === "rejected")
3890
+ throw new Error("Grok Bot rejected this task; check its conversation");
3891
+ return waitForReply();
3892
+ };
3893
+
3283
3894
  // apps/device/src/setup.ts
3284
- import { Effect as Effect5 } from "effect";
3895
+ import { Effect as Effect7 } from "effect";
3285
3896
  import { hostname as hostname2 } from "node:os";
3286
3897
  import { Writable } from "node:stream";
3287
3898
  import { createInterface } from "node:readline/promises";
@@ -3295,7 +3906,7 @@ var joinRelay = async (options) => {
3295
3906
  if (!options.clientOnly && draft.workspaces.length === 0) {
3296
3907
  throw new Error("At least one workspace is required when joining a device");
3297
3908
  }
3298
- const paired = await Effect5.runPromise(exchangePairing(draft.relayUrl, { token: options.token }));
3909
+ const paired = await Effect7.runPromise(exchangePairing(draft.relayUrl, { token: options.token }));
3299
3910
  const clientToken = paired.credentials.find((credential) => credential.session.role === "client")?.token;
3300
3911
  const deviceCredential = paired.credentials.find((credential) => credential.session.role === "device");
3301
3912
  if (options.clientOnly && clientToken === undefined) {
@@ -3378,15 +3989,15 @@ var guidedSetupInput = async (options, prompter) => {
3378
3989
 
3379
3990
  // apps/device/src/relay-migration.ts
3380
3991
  init_src();
3381
- import { Effect as Effect6, Schema as Schema7 } from "effect";
3992
+ import { Effect as Effect8, Schema as Schema9 } from "effect";
3382
3993
  import { createHash } from "node:crypto";
3383
3994
  import { createReadStream } from "node:fs";
3384
3995
  import {
3385
3996
  access as access3,
3386
- chmod as chmod5,
3997
+ chmod as chmod6,
3387
3998
  copyFile,
3388
3999
  lstat,
3389
- mkdir as mkdir4,
4000
+ mkdir as mkdir5,
3390
4001
  mkdtemp as mkdtemp2,
3391
4002
  readFile as readFile3,
3392
4003
  realpath as realpath4,
@@ -3395,24 +4006,24 @@ import {
3395
4006
  stat as stat2,
3396
4007
  writeFile as writeFile5
3397
4008
  } from "node:fs/promises";
3398
- import { basename, dirname as dirname4, join as join5, resolve as resolve2 } from "node:path";
4009
+ import { basename, dirname as dirname5, join as join6, resolve as resolve2 } from "node:path";
3399
4010
  import { backup as sqliteBackup, DatabaseSync } from "node:sqlite";
3400
4011
  import { WebSocket } from "ws";
3401
4012
 
3402
4013
  // apps/device/src/service.ts
3403
4014
  import { execFile as execFile3 } from "node:child_process";
3404
- import { chmod as chmod4, mkdir as mkdir3, realpath as realpath3, writeFile as writeFile4 } from "node:fs/promises";
4015
+ import { chmod as chmod5, mkdir as mkdir4, realpath as realpath3, writeFile as writeFile4 } from "node:fs/promises";
3405
4016
  import { homedir as homedir3 } from "node:os";
3406
- import { dirname as dirname3, join as join4 } from "node:path";
4017
+ import { dirname as dirname4, join as join5 } from "node:path";
3407
4018
 
3408
4019
  // apps/device/src/upgrade.ts
3409
- import { Schema as Schema6 } from "effect";
4020
+ import { Schema as Schema8 } from "effect";
3410
4021
  import { execFile as execFile2 } from "node:child_process";
3411
4022
  import { constants as constants2 } from "node:fs";
3412
4023
  import {
3413
4024
  access as access2,
3414
- chmod as chmod3,
3415
- mkdir as mkdir2,
4025
+ chmod as chmod4,
4026
+ mkdir as mkdir3,
3416
4027
  readFile as readFile2,
3417
4028
  realpath as realpath2,
3418
4029
  rename as rename2,
@@ -3420,18 +4031,18 @@ import {
3420
4031
  stat,
3421
4032
  writeFile as writeFile3
3422
4033
  } from "node:fs/promises";
3423
- import { delimiter as delimiter2, dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join3, relative } from "node:path";
4034
+ import { delimiter as delimiter2, dirname as dirname3, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative } from "node:path";
3424
4035
  import { platform as operatingSystem } from "node:os";
3425
4036
  var packageName = "@akshar5/cohall";
3426
4037
  var semanticVersion = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
3427
- var PackageManager = Schema6.Literals(["npm", "bun", "pnpm"]);
3428
- var RestartReceipt = Schema6.Struct({
3429
- version: Schema6.String,
3430
- fromVersion: Schema6.String,
4038
+ var PackageManager = Schema8.Literals(["npm", "bun", "pnpm"]);
4039
+ var RestartReceipt = Schema8.Struct({
4040
+ version: Schema8.String,
4041
+ fromVersion: Schema8.String,
3431
4042
  packageManager: PackageManager,
3432
- pendingServices: Schema6.Array(Schema6.String),
3433
- restartedServices: Schema6.Array(Schema6.String),
3434
- restartingService: Schema6.optionalKey(Schema6.String)
4043
+ pendingServices: Schema8.Array(Schema8.String),
4044
+ restartedServices: Schema8.Array(Schema8.String),
4045
+ restartingService: Schema8.optionalKey(Schema8.String)
3435
4046
  });
3436
4047
  var normalizePath = (path) => path.replaceAll("\\", "/");
3437
4048
  var normalizeUpgradeTarget = (target) => {
@@ -3505,13 +4116,13 @@ var linuxSystemExecutableRoots = [
3505
4116
  ];
3506
4117
  var macosHomebrewRoots = ["/opt/homebrew", "/usr/local"];
3507
4118
  var macosAdminGroup = 80;
3508
- var isTrustedSystemExecutablePath = (platform3, canonical) => platform3 === "linux" && linuxSystemExecutableRoots.some((root) => containsPath(root, canonical));
4119
+ var isTrustedSystemExecutablePath = (platform4, canonical) => platform4 === "linux" && linuxSystemExecutableRoots.some((root) => containsPath(root, canonical));
3509
4120
  var isTrustedGroupWritablePath = (options) => {
3510
4121
  const writableRoot = options.writableRoot;
3511
4122
  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);
3512
4123
  };
3513
4124
  var trustedExecutable = async (command, options = {}) => {
3514
- const candidates = isAbsolute2(command) ? [command] : (process.env.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((directory) => executableNames(command).map((name) => join3(directory, name)));
4125
+ const candidates = isAbsolute2(command) ? [command] : (process.env.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((directory) => executableNames(command).map((name) => join4(directory, name)));
3515
4126
  let selected;
3516
4127
  for (const path of candidates) {
3517
4128
  const available2 = await access2(path, operatingSystem() === "win32" ? constants2.F_OK : constants2.X_OK).then(() => true).catch(() => false);
@@ -3530,7 +4141,7 @@ var trustedExecutable = async (command, options = {}) => {
3530
4141
  const uid = process.getuid?.();
3531
4142
  const writableRoot = options.writableRoot === undefined ? undefined : await realpath2(options.writableRoot);
3532
4143
  const systemExecutable = isTrustedSystemExecutablePath(operatingSystem(), canonical);
3533
- for (let path = canonical;; path = dirname2(path)) {
4144
+ for (let path = canonical;; path = dirname3(path)) {
3534
4145
  const metadata = await stat(path);
3535
4146
  const trustedGroupWritablePath = isTrustedGroupWritablePath({
3536
4147
  platform: operatingSystem(),
@@ -3547,7 +4158,7 @@ var trustedExecutable = async (command, options = {}) => {
3547
4158
  if (uid !== undefined && metadata.uid !== 0 && metadata.uid !== uid && !systemExecutable) {
3548
4159
  throw new Error(`Refusing executable owned by another user at ${path}`);
3549
4160
  }
3550
- const parent = dirname2(path);
4161
+ const parent = dirname3(path);
3551
4162
  if (parent === path) {
3552
4163
  break;
3553
4164
  }
@@ -3712,15 +4323,15 @@ ${result.stderr}`);
3712
4323
  throw new Error(`Active ${service.label} uses ${serviceEntrypoint}, but this Cohall CLI uses ${entrypoint}. Run the service executable's upgrade command so its installation is updated before restart.`);
3713
4324
  }
3714
4325
  };
3715
- var readReceipt = async (path) => readFile2(path, "utf8").then((content) => Schema6.decodeUnknownOption(RestartReceipt)(JSON.parse(content))).then((decoded) => decoded._tag === "Some" ? decoded.value : undefined).catch(() => {
4326
+ var readReceipt = async (path) => readFile2(path, "utf8").then((content) => Schema8.decodeUnknownOption(RestartReceipt)(JSON.parse(content))).then((decoded) => decoded._tag === "Some" ? decoded.value : undefined).catch(() => {
3716
4327
  return;
3717
4328
  });
3718
4329
  var writeReceipt = async (path, value) => {
3719
- const directory = dirname2(path);
4330
+ const directory = dirname3(path);
3720
4331
  const temporary = `${path}.${process.pid}.tmp`;
3721
- await mkdir2(directory, { recursive: true, mode: 448 });
4332
+ await mkdir3(directory, { recursive: true, mode: 448 });
3722
4333
  if (operatingSystem() !== "win32") {
3723
- await chmod3(directory, 448);
4334
+ await chmod4(directory, 448);
3724
4335
  }
3725
4336
  await writeFile3(temporary, `${JSON.stringify(value, null, 2)}
3726
4337
  `, { mode: 384 });
@@ -3728,13 +4339,13 @@ var writeReceipt = async (path, value) => {
3728
4339
  };
3729
4340
  var installedVersion = async (entrypoint) => {
3730
4341
  const canonicalEntrypoint = await realpath2(entrypoint);
3731
- const metadata = JSON.parse(await readFile2(join3(dirname2(dirname2(canonicalEntrypoint)), "package.json"), "utf8"));
4342
+ const metadata = JSON.parse(await readFile2(join4(dirname3(dirname3(canonicalEntrypoint)), "package.json"), "utf8"));
3732
4343
  if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || metadata.name !== packageName || !("version" in metadata) || typeof metadata.version !== "string") {
3733
4344
  throw new Error(`Could not verify the installed ${packageName} version`);
3734
4345
  }
3735
4346
  return metadata.version;
3736
4347
  };
3737
- var restartReceiptPath = () => join3(dirname2(configurationPath()), "upgrade-restart.json");
4348
+ var restartReceiptPath = () => join4(dirname3(configurationPath()), "upgrade-restart.json");
3738
4349
  var restartServices = async (runner, services, statePath, state, preserveDeviceReceipt) => {
3739
4350
  let current = state;
3740
4351
  for (const service of services) {
@@ -3905,7 +4516,7 @@ var xml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").repl
3905
4516
  var systemdArgument = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
3906
4517
  var deviceServicePlan = (options) => {
3907
4518
  if (options.platform === "linux") {
3908
- const path = join4(options.home, ".config", "systemd", "user", "cohall-device.service");
4519
+ const path = join5(options.home, ".config", "systemd", "user", "cohall-device.service");
3909
4520
  return {
3910
4521
  file: {
3911
4522
  path,
@@ -3938,7 +4549,7 @@ WantedBy=default.target
3938
4549
  }
3939
4550
  if (options.platform === "darwin") {
3940
4551
  const label = "com.cohall.device";
3941
- const path = join4(options.home, "Library", "LaunchAgents", `${label}.plist`);
4552
+ const path = join5(options.home, "Library", "LaunchAgents", `${label}.plist`);
3942
4553
  const domain = `gui/${options.uid ?? 0}`;
3943
4554
  return {
3944
4555
  file: {
@@ -4009,10 +4620,10 @@ var checked2 = async (runner, command, arguments_, allowFailure = false) => {
4009
4620
  var installDeviceService = async (options = {}) => {
4010
4621
  const entrypoint = await realpath3(options.entrypoint ?? process.argv[1] ?? "");
4011
4622
  packageInstallation(entrypoint);
4012
- const platform3 = options.platform ?? process.platform;
4623
+ const platform4 = options.platform ?? process.platform;
4013
4624
  const runner = options.runner ?? defaultRunner2;
4014
- if (platform3 === "win32") {
4015
- const script = join4(dirname3(dirname3(entrypoint)), "deploy", "windows", "install-device.ps1");
4625
+ if (platform4 === "win32") {
4626
+ const script = join5(dirname4(dirname4(entrypoint)), "deploy", "windows", "install-device.ps1");
4016
4627
  await checked2(runner, "powershell.exe", [
4017
4628
  "-NoProfile",
4018
4629
  "-ExecutionPolicy",
@@ -4024,16 +4635,16 @@ var installDeviceService = async (options = {}) => {
4024
4635
  }
4025
4636
  const uid = options.uid ?? process.getuid?.();
4026
4637
  const plan = deviceServicePlan({
4027
- platform: platform3,
4638
+ platform: platform4,
4028
4639
  entrypoint,
4029
4640
  home: options.home ?? homedir3(),
4030
4641
  ...uid === undefined ? {} : { uid }
4031
4642
  });
4032
- await mkdir3(dirname3(plan.file.path), { recursive: true, mode: 448 });
4643
+ await mkdir4(dirname4(plan.file.path), { recursive: true, mode: 448 });
4033
4644
  await writeFile4(plan.file.path, plan.file.content, { mode: plan.file.mode });
4034
- await chmod4(plan.file.path, plan.file.mode);
4645
+ await chmod5(plan.file.path, plan.file.mode);
4035
4646
  for (const [index, command] of plan.commands.entries()) {
4036
- await checked2(runner, command.command, command.arguments, platform3 === "darwin" && index === 0);
4647
+ await checked2(runner, command.command, command.arguments, platform4 === "darwin" && index === 0);
4037
4648
  }
4038
4649
  return {
4039
4650
  installed: plan.file.path,
@@ -4041,9 +4652,9 @@ var installDeviceService = async (options = {}) => {
4041
4652
  };
4042
4653
  };
4043
4654
  var restartDeviceService = async (options = {}) => {
4044
- const platform3 = options.platform ?? process.platform;
4655
+ const platform4 = options.platform ?? process.platform;
4045
4656
  const runner = options.runner ?? defaultRunner2;
4046
- if (platform3 === "linux") {
4657
+ if (platform4 === "linux") {
4047
4658
  const service = "cohall-device.service";
4048
4659
  const check = await runner.run("systemctl", ["--user", "is-active", "--quiet", service], 1e4);
4049
4660
  if (check.exitCode !== 0) {
@@ -4052,7 +4663,7 @@ var restartDeviceService = async (options = {}) => {
4052
4663
  await checked2(runner, "systemctl", ["--user", "restart", service]);
4053
4664
  return { running: true, restarted: true, service };
4054
4665
  }
4055
- if (platform3 === "darwin") {
4666
+ if (platform4 === "darwin") {
4056
4667
  const service = "com.cohall.device";
4057
4668
  const target = `gui/${options.uid ?? process.getuid?.() ?? 0}/${service}`;
4058
4669
  const check = await runner.run("launchctl", ["print", target], 1e4);
@@ -4062,7 +4673,7 @@ var restartDeviceService = async (options = {}) => {
4062
4673
  await checked2(runner, "launchctl", ["kickstart", "-k", target]);
4063
4674
  return { running: true, restarted: true, service };
4064
4675
  }
4065
- if (platform3 === "win32") {
4676
+ if (platform4 === "win32") {
4066
4677
  const service = "Cohall Device";
4067
4678
  const check = await runner.run("schtasks.exe", ["/Query", "/TN", service], 1e4);
4068
4679
  if (check.exitCode !== 0) {
@@ -4076,10 +4687,10 @@ var restartDeviceService = async (options = {}) => {
4076
4687
  };
4077
4688
 
4078
4689
  // apps/device/src/relay-migration.ts
4079
- var sha256 = Schema7.String.check(Schema7.isPattern(/^[a-f0-9]{64}$/, { expected: "a lowercase SHA-256 digest" }));
4080
- var RelayBackupManifest = Schema7.Struct({
4081
- formatVersion: Schema7.Literal(1),
4082
- cohallVersion: Schema7.NonEmptyString,
4690
+ var sha256 = Schema9.String.check(Schema9.isPattern(/^[a-f0-9]{64}$/, { expected: "a lowercase SHA-256 digest" }));
4691
+ var RelayBackupManifest = Schema9.Struct({
4692
+ formatVersion: Schema9.Literal(1),
4693
+ cohallVersion: Schema9.NonEmptyString,
4083
4694
  createdAt: Timestamp,
4084
4695
  databaseSha256: sha256,
4085
4696
  ownerTokenSha256: sha256
@@ -4103,27 +4714,27 @@ var verifyBackupMember = async (path, maximumBytes) => {
4103
4714
  };
4104
4715
  var readManifest = async (path) => {
4105
4716
  await verifyBackupMember(path, 16384);
4106
- return Schema7.decodeUnknownSync(RelayBackupManifest)(JSON.parse(await readFile3(path, "utf8")));
4717
+ return Schema9.decodeUnknownSync(RelayBackupManifest)(JSON.parse(await readFile3(path, "utf8")));
4107
4718
  };
4108
4719
  var verifyBackupDestinationParent = async (path) => {
4109
4720
  if (process.platform === "win32") {
4110
4721
  return;
4111
4722
  }
4112
- for (let current = await realpath4(path);; current = dirname4(current)) {
4723
+ for (let current = await realpath4(path);; current = dirname5(current)) {
4113
4724
  const metadata = await stat2(current);
4114
4725
  const writableByOthers = (metadata.mode & 18) !== 0;
4115
4726
  const sticky = (metadata.mode & 512) !== 0;
4116
4727
  if (writableByOthers && !sticky) {
4117
4728
  throw new Error(`Backup destination ancestors must not be group- or world-writable unless they use the sticky bit: ${current}`);
4118
4729
  }
4119
- const parent = dirname4(current);
4730
+ const parent = dirname5(current);
4120
4731
  if (parent === current) {
4121
4732
  return;
4122
4733
  }
4123
4734
  }
4124
4735
  };
4125
4736
  var ownerToken = async (dataDirectory) => {
4126
- const path = join5(dataDirectory, "owner-token");
4737
+ const path = join6(dataDirectory, "owner-token");
4127
4738
  const configured = process.env.COHALL_TOKEN;
4128
4739
  if (configured !== undefined) {
4129
4740
  return configured;
@@ -4149,7 +4760,7 @@ var verifyDatabase = (path) => {
4149
4760
  };
4150
4761
  var backupRelay = async (destinationInput) => {
4151
4762
  const sourceDirectory = relayDataDirectory();
4152
- const sourceDatabase = join5(sourceDirectory, "cohall.db");
4763
+ const sourceDatabase = join6(sourceDirectory, "cohall.db");
4153
4764
  if (!await exists(sourceDatabase)) {
4154
4765
  throw new Error(`Relay database was not found at ${sourceDatabase}`);
4155
4766
  }
@@ -4157,11 +4768,11 @@ var backupRelay = async (destinationInput) => {
4157
4768
  if (await exists(destination)) {
4158
4769
  throw new Error(`Backup destination already exists: ${destination}`);
4159
4770
  }
4160
- await verifyBackupDestinationParent(dirname4(destination));
4161
- await mkdir4(destination, { mode: 448 });
4771
+ await verifyBackupDestinationParent(dirname5(destination));
4772
+ await mkdir5(destination, { mode: 448 });
4162
4773
  try {
4163
- const databasePath = join5(destination, "cohall.db");
4164
- const ownerTokenPath = join5(destination, "owner-token");
4774
+ const databasePath = join6(destination, "cohall.db");
4775
+ const ownerTokenPath = join6(destination, "owner-token");
4165
4776
  const database = new DatabaseSync(sourceDatabase, { readOnly: true });
4166
4777
  try {
4167
4778
  await sqliteBackup(database, databasePath);
@@ -4175,7 +4786,7 @@ var backupRelay = async (destinationInput) => {
4175
4786
  }
4176
4787
  await writeFile5(ownerTokenPath, `${token}
4177
4788
  `, { mode: 384 });
4178
- await Promise.all([chmod5(databasePath, 384), chmod5(ownerTokenPath, 384)]);
4789
+ await Promise.all([chmod6(databasePath, 384), chmod6(ownerTokenPath, 384)]);
4179
4790
  const createdAt = Timestamp.make(new Date().toISOString());
4180
4791
  const manifest = RelayBackupManifest.make({
4181
4792
  formatVersion: 1,
@@ -4184,7 +4795,7 @@ var backupRelay = async (destinationInput) => {
4184
4795
  databaseSha256: await digest(databasePath),
4185
4796
  ownerTokenSha256: await digest(ownerTokenPath)
4186
4797
  });
4187
- await writeFile5(join5(destination, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
4798
+ await writeFile5(join6(destination, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
4188
4799
  `, {
4189
4800
  mode: 384
4190
4801
  });
@@ -4201,9 +4812,9 @@ var backupRelay = async (destinationInput) => {
4201
4812
  };
4202
4813
  var restoreRelay = async (sourceInput) => {
4203
4814
  const source = resolve2(sourceInput);
4204
- const sourceDatabase = join5(source, "cohall.db");
4205
- const sourceOwnerToken = join5(source, "owner-token");
4206
- const sourceManifest = join5(source, "manifest.json");
4815
+ const sourceDatabase = join6(source, "cohall.db");
4816
+ const sourceOwnerToken = join6(source, "owner-token");
4817
+ const sourceManifest = join6(source, "manifest.json");
4207
4818
  for (const path of [sourceDatabase, sourceOwnerToken, sourceManifest]) {
4208
4819
  if (!await exists(path)) {
4209
4820
  throw new Error(`Relay backup is missing ${basename(path)}`);
@@ -4218,24 +4829,24 @@ var restoreRelay = async (sourceInput) => {
4218
4829
  if (await exists(target)) {
4219
4830
  throw new Error(`Relay data directory already exists: ${target}. Restore into a new COHALL_DATA_DIR.`);
4220
4831
  }
4221
- const parent = dirname4(target);
4222
- await mkdir4(parent, { recursive: true, mode: 448 });
4223
- const staging = await mkdtemp2(join5(parent, `.${basename(target)}.restore-`));
4832
+ const parent = dirname5(target);
4833
+ await mkdir5(parent, { recursive: true, mode: 448 });
4834
+ const staging = await mkdtemp2(join6(parent, `.${basename(target)}.restore-`));
4224
4835
  let backupVersion = "";
4225
4836
  try {
4226
- await chmod5(staging, 448);
4227
- const databasePath = join5(staging, "cohall.db");
4228
- const ownerTokenPath = join5(staging, "owner-token");
4229
- const manifestPath = join5(staging, "migration-manifest.json");
4837
+ await chmod6(staging, 448);
4838
+ const databasePath = join6(staging, "cohall.db");
4839
+ const ownerTokenPath = join6(staging, "owner-token");
4840
+ const manifestPath = join6(staging, "migration-manifest.json");
4230
4841
  await Promise.all([
4231
4842
  copyFile(sourceDatabase, databasePath),
4232
4843
  copyFile(sourceOwnerToken, ownerTokenPath),
4233
4844
  copyFile(sourceManifest, manifestPath)
4234
4845
  ]);
4235
4846
  await Promise.all([
4236
- chmod5(databasePath, 384),
4237
- chmod5(ownerTokenPath, 384),
4238
- chmod5(manifestPath, 384)
4847
+ chmod6(databasePath, 384),
4848
+ chmod6(ownerTokenPath, 384),
4849
+ chmod6(manifestPath, 384)
4239
4850
  ]);
4240
4851
  const manifest = await readManifest(manifestPath);
4241
4852
  if (await digest(databasePath) !== manifest.databaseSha256 || await digest(ownerTokenPath) !== manifest.ownerTokenSha256) {
@@ -4267,7 +4878,7 @@ var websocketUrl = (relayUrl) => {
4267
4878
  };
4268
4879
  var isLoopback = (url) => url.hostname === "localhost" || url.hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(url.hostname);
4269
4880
  var verifyClientCredential = async (relayUrl, token) => {
4270
- await Effect6.runPromise(exports_src.make({ baseUrl: relayUrl, token }).devices());
4881
+ await Effect8.runPromise(exports_src.make({ baseUrl: relayUrl, token }).devices());
4271
4882
  };
4272
4883
  var verifyDeviceCredential = (relayUrl, token) => new Promise((resolvePromise, reject) => {
4273
4884
  const socket = new WebSocket(websocketUrl(relayUrl), {
@@ -4295,7 +4906,7 @@ var verifyDeviceCredential = (relayUrl, token) => new Promise((resolvePromise, r
4295
4906
  });
4296
4907
  socket.on("message", (data) => {
4297
4908
  try {
4298
- const event = Schema7.decodeUnknownSync(SocketEvent)(JSON.parse(data.toString()));
4909
+ const event = Schema9.decodeUnknownSync(SocketEvent)(JSON.parse(data.toString()));
4299
4910
  if (event._tag === "Connected") {
4300
4911
  finish();
4301
4912
  } else if (event._tag === "Error") {
@@ -4364,9 +4975,14 @@ var switchRelay = async (options) => {
4364
4975
  var valueOptions = new Set([
4365
4976
  "context",
4366
4977
  "context-file",
4978
+ "grok-gateway",
4979
+ "error",
4367
4980
  "label",
4368
4981
  "model",
4982
+ "message",
4983
+ "message-file",
4369
4984
  "name",
4985
+ "parent",
4370
4986
  "prompt",
4371
4987
  "prompt-file",
4372
4988
  "provider",
@@ -4404,15 +5020,19 @@ Usage:
4404
5020
  cohall join --relay <url> --workspace <path> [--providers list] [--token-file <path>]
4405
5021
  cohall configure [--relay url] [--name name] [--workspace path]
4406
5022
  [--providers codex,opencode|auto] [--model id]
5023
+ [--grok-gateway path]
4407
5024
  cohall config
4408
5025
  cohall devices
4409
- cohall delegate [prompt] [--target @device] [--provider provider]
4410
- [--context text] [--thread uuid] [--workspace path]
5026
+ cohall bots
5027
+ cohall send [@device-or-bot] [prompt] [delegate options]
5028
+ cohall delegate [prompt] [--target @device-or-bot] [--provider provider]
5029
+ [--context text] [--thread uuid] [--parent task-id] [--workspace path]
4411
5030
  [--timeout seconds] [--no-wait]
4412
5031
  cohall status <task-id>
4413
5032
  cohall trace <task-id> [--follow]
4414
5033
  cohall wait <task-id> [--timeout seconds]
4415
5034
  cohall cancel <task-id>
5035
+ cohall reply <task-id> --message-file path | --message text | --message - | --error text
4416
5036
  cohall thread <thread-id>
4417
5037
  cohall pair [--label name] [--client-only]
4418
5038
  cohall sessions
@@ -4434,7 +5054,7 @@ Usage:
4434
5054
  cohall skill [install [agents|claude|opencode|all]]
4435
5055
  cohall integrations
4436
5056
 
4437
- Providers: codex, claude-code, opencode.
5057
+ Providers: codex, claude-code, opencode, grok-bot.
4438
5058
  Configuration is stored per user; environment variables override it.`;
4439
5059
  var parseArguments = (values) => {
4440
5060
  const options = new Map;
@@ -4520,6 +5140,7 @@ var printLine = (value) => console.log(JSON.stringify(value));
4520
5140
  var readInput = async (arguments_, name) => {
4521
5141
  const direct = option(arguments_, name);
4522
5142
  const path = option(arguments_, `${name}-file`);
5143
+ const byteLimit = name === "message" ? 4 * 131072 : 131072;
4523
5144
  if (direct !== undefined && path !== undefined) {
4524
5145
  throw new Error(`Use either --${name} or --${name}-file, not both`);
4525
5146
  }
@@ -4527,14 +5148,13 @@ var readInput = async (arguments_, name) => {
4527
5148
  if (!await access4(path).then(() => true).catch(() => false)) {
4528
5149
  throw new Error(`File does not exist: ${path}`);
4529
5150
  }
4530
- if ((await stat3(path)).size > 131072) {
4531
- throw new Error(`${name} file exceeds 128 KiB`);
5151
+ if ((await stat3(path)).size > byteLimit) {
5152
+ throw new Error(`${name} file exceeds ${byteLimit / 1024} KiB`);
4532
5153
  }
4533
- return readFile4(path, "utf8");
4534
5154
  }
4535
- const result = direct === "-" ? await readStdin(131072, name) : direct;
5155
+ const result = path === undefined ? direct === "-" ? await readStdin(byteLimit, name) : direct : await readFile4(path, "utf8");
4536
5156
  if (result !== undefined && result.length > 131072) {
4537
- throw new Error(`${name} exceeds 128 KiB`);
5157
+ throw new Error(`${name} exceeds 131072 characters`);
4538
5158
  }
4539
5159
  return result;
4540
5160
  };
@@ -4573,14 +5193,14 @@ var pairingToken = async (arguments_) => {
4573
5193
  return token;
4574
5194
  };
4575
5195
  var client = async () => {
4576
- const configuration = await Effect7.runPromise(loadClientConfiguration);
5196
+ const configuration = await Effect9.runPromise(loadClientConfiguration);
4577
5197
  return {
4578
5198
  configuration,
4579
5199
  relay: exports_src.make({ baseUrl: configuration.relayUrl, token: configuration.token })
4580
5200
  };
4581
5201
  };
4582
5202
  var ownerClient = async () => {
4583
- const configuration = await Effect7.runPromise(loadOwnerConfiguration);
5203
+ const configuration = await Effect9.runPromise(loadOwnerConfiguration);
4584
5204
  return {
4585
5205
  configuration,
4586
5206
  relay: exports_src.make({ baseUrl: configuration.relayUrl, token: configuration.token })
@@ -4588,16 +5208,16 @@ var ownerClient = async () => {
4588
5208
  };
4589
5209
  var installSkill = async (target) => {
4590
5210
  const roots = {
4591
- agents: join6(homedir4(), ".agents", "skills", "cohall", "SKILL.md"),
4592
- claude: join6(homedir4(), ".claude", "skills", "cohall", "SKILL.md"),
4593
- opencode: join6(homedir4(), ".config", "opencode", "skills", "cohall", "SKILL.md")
5211
+ agents: join7(homedir4(), ".agents", "skills", "cohall", "SKILL.md"),
5212
+ claude: join7(homedir4(), ".claude", "skills", "cohall", "SKILL.md"),
5213
+ opencode: join7(homedir4(), ".config", "opencode", "skills", "cohall", "SKILL.md")
4594
5214
  };
4595
5215
  const selected = target === "all" ? Object.values(roots) : (target in roots) ? [roots[target]] : undefined;
4596
5216
  if (selected === undefined) {
4597
5217
  throw new Error("Skill target must be agents, claude, opencode, or all");
4598
5218
  }
4599
5219
  for (const path of selected) {
4600
- await mkdir5(dirname5(path), { recursive: true });
5220
+ await mkdir6(dirname6(path), { recursive: true });
4601
5221
  await writeFile6(path, SKILL_default);
4602
5222
  }
4603
5223
  return selected;
@@ -4606,6 +5226,21 @@ var printHelp = () => console.log(help);
4606
5226
  var printSkill = () => console.log(SKILL_default.trimEnd());
4607
5227
  var runCli = async (command, raw) => {
4608
5228
  const arguments_ = parseArguments(raw);
5229
+ if (command === "reply") {
5230
+ allowOptions(arguments_, ["message", "message-file", "error"]);
5231
+ const id = Schema10.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
5232
+ const error = option(arguments_, "error");
5233
+ if (error !== undefined && (option(arguments_, "message") !== undefined || option(arguments_, "message-file") !== undefined)) {
5234
+ throw new Error("Use either --error or --message/--message-file, not both");
5235
+ }
5236
+ const message = error === undefined ? await readInput(arguments_, "message") : undefined;
5237
+ if (error === undefined && message === undefined) {
5238
+ throw new Error("A bot reply requires --message, --message-file, or --error");
5239
+ }
5240
+ await writeBotReply(id, error === undefined ? { result: message ?? "" } : { error });
5241
+ print({ submitted: true, task_id: id });
5242
+ return;
5243
+ }
4609
5244
  if (command === "init") {
4610
5245
  allowOptions(arguments_, [
4611
5246
  "client-only",
@@ -4731,7 +5366,7 @@ var runCli = async (command, raw) => {
4731
5366
  const { relay: relay2 } = await ownerClient();
4732
5367
  const restart = !arguments_.options.has("no-restart");
4733
5368
  if (arguments_.options.has("dry-run")) {
4734
- const devices = await Effect7.runPromise(relay2.devices());
5369
+ const devices = await Effect9.runPromise(relay2.devices());
4735
5370
  assertDeviceOperationSupport(devices);
4736
5371
  print({
4737
5372
  dry_run: true,
@@ -4741,7 +5376,7 @@ var runCli = async (command, raw) => {
4741
5376
  });
4742
5377
  return;
4743
5378
  }
4744
- const operations = await Effect7.runPromise(relay2.createUpgradeOperations({ target: normalizedTarget, restart }));
5379
+ const operations = await Effect9.runPromise(relay2.createUpgradeOperations({ target: normalizedTarget, restart }));
4745
5380
  print({
4746
5381
  queued: operations.length,
4747
5382
  requested_version: normalizedTarget,
@@ -4767,10 +5402,10 @@ var runCli = async (command, raw) => {
4767
5402
  if (arguments_.positionals.length !== 2 || arguments_.positionals[0] !== "abandon") {
4768
5403
  throw new Error("Usage: cohall upgrades [abandon <operation-id>]");
4769
5404
  }
4770
- print(await Effect7.runPromise(relay2.abandonOperation(Schema8.decodeUnknownSync(OperationId)(arguments_.positionals[1]))));
5405
+ print(await Effect9.runPromise(relay2.abandonOperation(Schema10.decodeUnknownSync(OperationId)(arguments_.positionals[1]))));
4771
5406
  return;
4772
5407
  }
4773
- print(await Effect7.runPromise(relay2.operations()));
5408
+ print(await Effect9.runPromise(relay2.operations()));
4774
5409
  return;
4775
5410
  }
4776
5411
  if (command === "join") {
@@ -4829,20 +5464,31 @@ var runCli = async (command, raw) => {
4829
5464
  device_credential: configuration2.deviceToken !== undefined,
4830
5465
  providers: configuration2.providers ?? "auto",
4831
5466
  model: configuration2.model,
4832
- sandbox: configuration2.sandbox
5467
+ sandbox: configuration2.sandbox,
5468
+ grok_gateway: configuration2.grokGateway
4833
5469
  }
4834
5470
  });
4835
5471
  return;
4836
5472
  }
4837
5473
  if (command === "configure") {
4838
- allowOptions(arguments_, ["model", "name", "providers", "relay", "sandbox", "workspace"]);
5474
+ allowOptions(arguments_, [
5475
+ "grok-gateway",
5476
+ "model",
5477
+ "name",
5478
+ "providers",
5479
+ "relay",
5480
+ "sandbox",
5481
+ "workspace"
5482
+ ]);
4839
5483
  noPositionals(arguments_, command);
4840
5484
  const existing = await readStoredConfiguration();
4841
5485
  const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? existing?.relayUrl ?? "http://127.0.0.1:8787");
4842
5486
  const supplied = values(arguments_, "workspace");
4843
- const workspaces = supplied.length === 0 ? existing?.workspaces ?? [] : await Effect7.runPromise(parseWorkspaces("", JSON.stringify(supplied)));
5487
+ const workspaces = supplied.length === 0 ? existing?.workspaces ?? [] : await Effect9.runPromise(parseWorkspaces("", JSON.stringify(supplied)));
4844
5488
  const sandbox = option(arguments_, "sandbox");
4845
5489
  const model = option(arguments_, "model") ?? existing?.model;
5490
+ const gatewayInput = option(arguments_, "grok-gateway") ?? existing?.grokGateway;
5491
+ const grokGateway = gatewayInput === undefined ? undefined : resolve3(gatewayInput);
4846
5492
  const providerInput = option(arguments_, "providers");
4847
5493
  const providers = providerInput === undefined ? existing?.providers : providerInput === "auto" ? undefined : parseProviders(providerInput);
4848
5494
  const configuration2 = StoredConfiguration.make({
@@ -4854,8 +5500,9 @@ var runCli = async (command, raw) => {
4854
5500
  ...credentialsForRelay(existing, relayUrl),
4855
5501
  ...providers === undefined ? {} : { providers },
4856
5502
  ...model === undefined ? {} : { model },
5503
+ ...grokGateway === undefined ? {} : { grokGateway },
4857
5504
  ...(sandbox ?? existing?.sandbox) === undefined ? {} : {
4858
- sandbox: Schema8.decodeUnknownSync(Schema8.Literals(["read-only", "workspace-write", "danger-full-access"]))(sandbox ?? existing?.sandbox)
5505
+ sandbox: Schema10.decodeUnknownSync(Schema10.Literals(["read-only", "workspace-write", "danger-full-access"]))(sandbox ?? existing?.sandbox)
4859
5506
  }
4860
5507
  });
4861
5508
  await writeStoredConfiguration(configuration2);
@@ -4898,7 +5545,7 @@ var runCli = async (command, raw) => {
4898
5545
  noPositionals(arguments_, command);
4899
5546
  if (arguments_.options.has("all")) {
4900
5547
  const { relay: relay2 } = await client();
4901
- print(allDeviceHealth(await Effect7.runPromise(relay2.devices()), version));
5548
+ print(allDeviceHealth(await Effect9.runPromise(relay2.devices()), version));
4902
5549
  return;
4903
5550
  }
4904
5551
  const configuration2 = await readStoredConfiguration();
@@ -4911,14 +5558,18 @@ var runCli = async (command, raw) => {
4911
5558
  });
4912
5559
  const providerInput = process.env.COHALL_DEVICE_PROVIDERS;
4913
5560
  const selectedProviders = providerInput === undefined ? configuration2?.providers : providerInput === "auto" ? undefined : parseProviders(providerInput);
4914
- const providerExecutables = Object.fromEntries(Provider.literals.map((provider) => [
5561
+ const providerExecutables = Object.fromEntries(Provider.literals.filter((provider) => provider !== "grok-bot").map((provider) => [
4915
5562
  provider,
4916
5563
  findExecutable(provider === "claude-code" ? "claude" : provider) ?? "not found"
4917
5564
  ]));
5565
+ const grokGateway = process.env.COHALL_GROK_GATEWAY ?? configuration2?.grokGateway;
5566
+ const grokBots = grokGateway === undefined ? undefined : await discoverGrokBots(grokGateway).catch(() => {
5567
+ return;
5568
+ });
4918
5569
  const clientToken = process.env.COHALL_CLIENT_TOKEN ?? storedCredentials.clientToken;
4919
5570
  const hasDeviceCredential = process.env.COHALL_DEVICE_TOKEN !== undefined || storedCredentials.deviceToken !== undefined;
4920
5571
  const deviceId = process.env.COHALL_DEVICE_ID ?? configuration2?.deviceId;
4921
- const currentDevice = response?.ok !== true || clientToken === undefined || deviceId === undefined ? undefined : await Effect7.runPromise(exports_src.make({ baseUrl: relayUrl, token: clientToken }).devices()).then((devices) => devices.find((device) => device.id === deviceId)).catch(() => {
5572
+ const currentDevice = response?.ok !== true || clientToken === undefined || deviceId === undefined ? undefined : await Effect9.runPromise(exports_src.make({ baseUrl: relayUrl, token: clientToken }).devices()).then((devices) => devices.find((device) => device.id === deviceId)).catch(() => {
4922
5573
  return;
4923
5574
  });
4924
5575
  const versionWarning = deviceVersionWarning(version, currentDevice?.version);
@@ -4928,7 +5579,9 @@ var runCli = async (command, raw) => {
4928
5579
  ...hasDeviceCredential && response?.ok === true && currentDevice?.status !== "online" && currentDevice?.status !== "busy" ? [
4929
5580
  clientToken === undefined ? "Device status cannot be checked without a client credential" : "Device daemon is not connected or is not registered with the relay"
4930
5581
  ] : [],
4931
- ...selectedProviders?.flatMap((provider) => providerExecutables[provider] === "not found" ? [`Configured provider ${provider} is not installed or not on PATH`] : []) ?? []
5582
+ ...selectedProviders?.flatMap((provider) => provider === "grok-bot" ? grokBots === undefined ? [
5583
+ "Configured Grok Bot gateway is unavailable; check its discovery file and host process"
5584
+ ] : [] : providerExecutables[provider] === "not found" ? [`Configured provider ${provider} is not installed or not on PATH`] : []) ?? []
4932
5585
  ];
4933
5586
  print({
4934
5587
  version,
@@ -4939,6 +5592,13 @@ var runCli = async (command, raw) => {
4939
5592
  device_credential: hasDeviceCredential,
4940
5593
  workspaces: configuration2?.workspaces ?? [],
4941
5594
  providers: providerExecutables,
5595
+ ...grokGateway === undefined ? {} : {
5596
+ grok_gateway: {
5597
+ path: grokGateway,
5598
+ reachable: grokBots !== undefined,
5599
+ bots: grokBots?.length ?? 0
5600
+ }
5601
+ },
4942
5602
  provider_selection: selectedProviders ?? "auto",
4943
5603
  provider_authentication: "checked when delegated work starts",
4944
5604
  device_status: hasDeviceCredential === false ? "not configured" : currentDevice?.status ?? "unknown",
@@ -4951,7 +5611,7 @@ var runCli = async (command, raw) => {
4951
5611
  allowOptions(arguments_, []);
4952
5612
  noPositionals(arguments_, command);
4953
5613
  const { relay: relay2 } = await client();
4954
- print(deviceVersions(await Effect7.runPromise(relay2.devices()), version));
5614
+ print(deviceVersions(await Effect9.runPromise(relay2.devices()), version));
4955
5615
  return;
4956
5616
  }
4957
5617
  if (command === "usage") {
@@ -4959,7 +5619,7 @@ var runCli = async (command, raw) => {
4959
5619
  noPositionals(arguments_, command);
4960
5620
  const { relay: relay2 } = await client();
4961
5621
  print({
4962
- ...await Effect7.runPromise(relay2.usage()),
5622
+ ...await Effect9.runPromise(relay2.usage()),
4963
5623
  scope: "Retained Cohall task activity; provider token and billing usage are not available."
4964
5624
  });
4965
5625
  return;
@@ -4970,7 +5630,7 @@ var runCli = async (command, raw) => {
4970
5630
  allowOptions(arguments_, ["client-only", "label"]);
4971
5631
  noPositionals(arguments_, command);
4972
5632
  const roles = arguments_.options.has("client-only") ? ["client"] : ["client", "device"];
4973
- const credential = await Effect7.runPromise(relay2.createPairing({ label: option(arguments_, "label") ?? "Cohall device", roles }));
5633
+ const credential = await Effect9.runPromise(relay2.createPairing({ label: option(arguments_, "label") ?? "Cohall device", roles }));
4974
5634
  print({
4975
5635
  relay_url: configuration2.relayUrl,
4976
5636
  pairing_token: credential.token,
@@ -4982,30 +5642,37 @@ var runCli = async (command, raw) => {
4982
5642
  if (command === "sessions") {
4983
5643
  allowOptions(arguments_, []);
4984
5644
  noPositionals(arguments_, command);
4985
- print(await Effect7.runPromise(relay2.authSessions()));
5645
+ print(await Effect9.runPromise(relay2.authSessions()));
4986
5646
  return;
4987
5647
  }
4988
5648
  if (command === "forget") {
4989
5649
  allowOptions(arguments_, []);
4990
- print(await Effect7.runPromise(relay2.forgetDevice(Schema8.decodeUnknownSync(DeviceId)(identifier(arguments_, "device id")))));
5650
+ print(await Effect9.runPromise(relay2.forgetDevice(Schema10.decodeUnknownSync(DeviceId)(identifier(arguments_, "device id")))));
4991
5651
  return;
4992
5652
  }
4993
5653
  allowOptions(arguments_, []);
4994
- print(await Effect7.runPromise(relay2.revokeAuthSession(Schema8.decodeUnknownSync(AuthSessionId)(identifier(arguments_, "session id")))));
5654
+ print(await Effect9.runPromise(relay2.revokeAuthSession(Schema10.decodeUnknownSync(AuthSessionId)(identifier(arguments_, "session id")))));
4995
5655
  return;
4996
5656
  }
4997
5657
  const { configuration, relay } = await client();
4998
5658
  if (command === "devices") {
4999
5659
  allowOptions(arguments_, []);
5000
5660
  noPositionals(arguments_, command);
5001
- print(await Effect7.runPromise(relay.devices()));
5661
+ print(await Effect9.runPromise(relay.devices()));
5002
5662
  return;
5003
5663
  }
5004
- if (command === "delegate") {
5664
+ if (command === "bots") {
5665
+ allowOptions(arguments_, []);
5666
+ noPositionals(arguments_, command);
5667
+ print(listBots(await Effect9.runPromise(relay.devices())));
5668
+ return;
5669
+ }
5670
+ if (command === "delegate" || command === "send") {
5005
5671
  allowOptions(arguments_, [
5006
5672
  "context",
5007
5673
  "context-file",
5008
5674
  "no-wait",
5675
+ "parent",
5009
5676
  "prompt",
5010
5677
  "prompt-file",
5011
5678
  "provider",
@@ -5014,66 +5681,73 @@ var runCli = async (command, raw) => {
5014
5681
  "timeout",
5015
5682
  "workspace"
5016
5683
  ]);
5684
+ const positionalTarget = command === "send" && arguments_.positionals[0]?.startsWith("@") ? arguments_.positionals[0] : undefined;
5685
+ const positionals = positionalTarget === undefined ? arguments_.positionals : arguments_.positionals.slice(1);
5686
+ if (positionalTarget !== undefined && option(arguments_, "target") !== undefined) {
5687
+ throw new Error("Use either a positional target or --target, not both");
5688
+ }
5017
5689
  if (option(arguments_, "prompt") === "-" && option(arguments_, "context") === "-") {
5018
5690
  throw new Error("Only one of --prompt and --context may read from stdin");
5019
5691
  }
5020
5692
  const suppliedPrompt = await readInput(arguments_, "prompt");
5021
- const prompt = suppliedPrompt ?? arguments_.positionals.join(" ");
5693
+ const prompt = suppliedPrompt ?? positionals.join(" ");
5022
5694
  if (prompt.trim().length === 0) {
5023
5695
  throw new Error("A prompt is required");
5024
5696
  }
5025
- if (suppliedPrompt !== undefined && arguments_.positionals.length > 0) {
5697
+ if (suppliedPrompt !== undefined && positionals.length > 0) {
5026
5698
  throw new Error("Use either a positional prompt or --prompt, not both");
5027
5699
  }
5028
5700
  const context = await readInput(arguments_, "context");
5029
- const target = option(arguments_, "target");
5701
+ const target = positionalTarget ?? option(arguments_, "target");
5030
5702
  const thread = option(arguments_, "thread");
5031
5703
  const workspace = option(arguments_, "workspace");
5032
5704
  const provider = option(arguments_, "provider");
5033
- const task = await Effect7.runPromise(createDelegation(relay, configuration, {
5705
+ const parent = option(arguments_, "parent");
5706
+ const task = await Effect9.runPromise(createDelegation(relay, configuration, {
5034
5707
  prompt,
5035
5708
  ...target === undefined ? {} : { target },
5036
5709
  ...context === undefined ? {} : { context },
5037
- ...thread === undefined ? {} : { threadId: Schema8.decodeUnknownSync(ThreadId)(thread) },
5710
+ ...thread === undefined ? {} : { threadId: Schema10.decodeUnknownSync(ThreadId)(thread) },
5711
+ ...parent === undefined ? {} : { parentTaskId: Schema10.decodeUnknownSync(TaskId)(parent) },
5038
5712
  ...workspace === undefined ? {} : { workspace },
5039
- ...provider === undefined ? {} : { provider: Schema8.decodeUnknownSync(Provider)(provider) }
5713
+ ...provider === undefined ? {} : { provider: Schema10.decodeUnknownSync(Provider)(provider) }
5040
5714
  }));
5041
- print(taskResult(arguments_.options.has("no-wait") ? task : await Effect7.runPromise(waitForTask(relay, task, timeout(arguments_)))));
5715
+ print(taskResult(arguments_.options.has("no-wait") ? task : await Effect9.runPromise(waitForTask(relay, task, timeout(arguments_)))));
5042
5716
  return;
5043
5717
  }
5044
5718
  if (command === "status") {
5045
5719
  allowOptions(arguments_, []);
5046
- const id = Schema8.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
5047
- print(taskResult(await Effect7.runPromise(relay.getTask(id))));
5720
+ const id = Schema10.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
5721
+ print(taskResult(await Effect9.runPromise(relay.getTask(id))));
5048
5722
  return;
5049
5723
  }
5050
5724
  if (command === "trace") {
5051
5725
  allowOptions(arguments_, ["follow"]);
5052
- const id = Schema8.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
5726
+ const id = Schema10.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
5053
5727
  if (arguments_.options.has("follow")) {
5054
- await Effect7.runPromise(followTaskTrace(relay, id, printLine));
5728
+ await Effect9.runPromise(followTaskTrace(relay, id, printLine));
5055
5729
  return;
5056
5730
  }
5057
- print(await Effect7.runPromise(relay.traceTask(id)));
5731
+ print(await Effect9.runPromise(relay.traceTask(id)));
5058
5732
  return;
5059
5733
  }
5060
5734
  if (command === "wait") {
5061
5735
  allowOptions(arguments_, ["timeout"]);
5062
- const id = Schema8.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
5063
- const task = await Effect7.runPromise(relay.getTask(id));
5064
- print(taskResult(await Effect7.runPromise(waitForTask(relay, task, timeout(arguments_)))));
5736
+ const id = Schema10.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
5737
+ const task = await Effect9.runPromise(relay.getTask(id));
5738
+ print(taskResult(await Effect9.runPromise(waitForTask(relay, task, timeout(arguments_)))));
5065
5739
  return;
5066
5740
  }
5067
5741
  if (command === "cancel") {
5068
5742
  allowOptions(arguments_, []);
5069
- const id = Schema8.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
5070
- print(taskResult(await Effect7.runPromise(relay.cancelTask(id))));
5743
+ const id = Schema10.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
5744
+ print(taskResult(await Effect9.runPromise(relay.cancelTask(id))));
5071
5745
  return;
5072
5746
  }
5073
5747
  if (command === "thread") {
5074
5748
  allowOptions(arguments_, []);
5075
- const id = Schema8.decodeUnknownSync(ThreadId)(identifier(arguments_, "thread id"));
5076
- print(await Effect7.runPromise(threadContext(relay, id)));
5749
+ const id = Schema10.decodeUnknownSync(ThreadId)(identifier(arguments_, "thread id"));
5750
+ print(await Effect9.runPromise(threadContext(relay, id)));
5077
5751
  return;
5078
5752
  }
5079
5753
  throw new Error(`Unknown Cohall command: ${command}`);
@@ -5081,15 +5755,15 @@ var runCli = async (command, raw) => {
5081
5755
 
5082
5756
  // apps/device/src/daemon.ts
5083
5757
  init_src();
5084
- import { Effect as Effect8, Schedule as Schedule2, Schema as Schema9 } from "effect";
5758
+ import { Effect as Effect10, Schedule as Schedule3, Schema as Schema11 } from "effect";
5085
5759
  import { constants as constants3 } from "node:fs";
5086
- import { open, realpath as realpath5, stat as stat4 } from "node:fs/promises";
5087
- import { arch, hostname as hostname3, platform as platform3 } from "node:os";
5760
+ import { open as open3, realpath as realpath5, stat as stat4 } from "node:fs/promises";
5761
+ import { arch, hostname as hostname3, platform as platform4 } from "node:os";
5088
5762
  import { basename as basename2, isAbsolute as isAbsolute3, relative as relative2 } from "node:path";
5089
5763
  import { WebSocket as WebSocket2 } from "ws";
5090
5764
  var maxQueuedRelayMessages = 8;
5091
5765
 
5092
- class DeviceConnectionError extends Schema9.TaggedErrorClass()("Device.ConnectionError", { message: Schema9.String }) {
5766
+ class DeviceConnectionError extends Schema11.TaggedErrorClass()("Device.ConnectionError", { message: Schema11.String }) {
5093
5767
  }
5094
5768
  var socketUrl = (configuration) => {
5095
5769
  const url = new URL(configuration.relayUrl);
@@ -5105,15 +5779,17 @@ var providerLabel = (provider) => {
5105
5779
  return "Claude Code";
5106
5780
  case "opencode":
5107
5781
  return "OpenCode";
5782
+ case "grok-bot":
5783
+ return "Grok Bot";
5108
5784
  }
5109
5785
  };
5110
5786
  var capabilities = (providers) => {
5111
5787
  const values2 = providers.map((provider) => ({
5112
5788
  id: provider,
5113
5789
  label: providerLabel(provider),
5114
- detail: `${providerLabel(provider)} executable detected; authentication is checked when work starts`
5790
+ detail: provider === "grok-bot" ? "Named bots through this computer's local Grok Bot gateway" : `${providerLabel(provider)} executable detected; authentication is checked when work starts`
5115
5791
  }));
5116
- if (findExecutable("google-chrome") !== undefined || findExecutable("chromium") !== undefined || platform3() === "darwin") {
5792
+ if (findExecutable("google-chrome") !== undefined || findExecutable("chromium") !== undefined || platform4() === "darwin") {
5117
5793
  values2.push({ id: "browser-session", label: "Signed-in browser" });
5118
5794
  }
5119
5795
  if (findExecutable("xcodebuild") !== undefined) {
@@ -5125,10 +5801,13 @@ var capabilities = (providers) => {
5125
5801
  return values2;
5126
5802
  };
5127
5803
  var selectProviders = (installed, configured) => configured === undefined ? installed : configured.filter((provider) => installed.includes(provider));
5128
- var describeDevice = (configuration, status = "online") => {
5129
- const operatingSystem2 = platform3();
5804
+ var describeDevice = (configuration, status = "online", bots = []) => {
5805
+ const operatingSystem2 = platform4();
5130
5806
  const platformName = operatingSystem2 === "darwin" || operatingSystem2 === "linux" ? operatingSystem2 : operatingSystem2 === "win32" ? "windows" : "unknown";
5131
- const installed = availableProviders();
5807
+ const installed = [
5808
+ ...availableProviders(),
5809
+ ...configuration.grokGateway === undefined ? [] : ["grok-bot"]
5810
+ ];
5132
5811
  const providers = selectProviders(installed, configuration.providers);
5133
5812
  return Device.make({
5134
5813
  id: configuration.id,
@@ -5138,6 +5817,7 @@ var describeDevice = (configuration, status = "online") => {
5138
5817
  architecture: arch(),
5139
5818
  status,
5140
5819
  providers,
5820
+ ...providers.includes("grok-bot") ? { bots } : {},
5141
5821
  capabilities: capabilities(providers),
5142
5822
  workspaces: configuration.workspaces.map((path) => ({
5143
5823
  path,
@@ -5164,9 +5844,9 @@ var allowedWorkspace = async (configuration, requested) => {
5164
5844
  };
5165
5845
  var openAllowedWorkspace = async (configuration, requested) => {
5166
5846
  const path = await allowedWorkspace(configuration, requested);
5167
- const operatingSystem2 = platform3();
5847
+ const operatingSystem2 = platform4();
5168
5848
  const flags = constants3.O_RDONLY | (operatingSystem2 === "win32" ? 0 : constants3.O_DIRECTORY | constants3.O_NOFOLLOW);
5169
- const handle = await open(path, flags);
5849
+ const handle = await open3(path, flags);
5170
5850
  const identity = await handle.stat();
5171
5851
  if (!identity.isDirectory()) {
5172
5852
  await handle.close();
@@ -5249,15 +5929,47 @@ var rememberOperation = (state, operationId) => {
5249
5929
  }
5250
5930
  };
5251
5931
  var sessionKey = (task) => `${task.threadId}:${task.provider}`;
5932
+ var drain = (configuration, state) => {
5933
+ if (state.operation !== undefined) {
5934
+ return;
5935
+ }
5936
+ if (state.tasks.size === 0) {
5937
+ const operation = state.operationQueue.shift();
5938
+ if (operation !== undefined) {
5939
+ executeOperation(configuration, state, operation);
5940
+ return;
5941
+ }
5942
+ }
5943
+ const occupied = new Set([...state.tasks.values()].map(({ task }) => taskSlot(task)));
5944
+ for (let index = 0;index < state.queue.length; ) {
5945
+ const next = state.queue[index];
5946
+ if (next === undefined || occupied.has(taskSlot(next))) {
5947
+ index += 1;
5948
+ continue;
5949
+ }
5950
+ state.queue.splice(index, 1);
5951
+ occupied.add(taskSlot(next));
5952
+ execute(configuration, state, next);
5953
+ }
5954
+ };
5252
5955
  var execute = (configuration, state, task) => {
5253
5956
  if (state.tasks.has(task.id) || state.completed.has(task.id)) {
5254
5957
  return;
5255
5958
  }
5256
5959
  const controller = new AbortController;
5257
- state.tasks.set(task.id, controller);
5960
+ state.tasks.set(task.id, { task, controller });
5258
5961
  send(state, SocketEvent.make({ _tag: "TaskAccepted", taskId: task.id }));
5259
- const workflow = Effect8.gen(function* () {
5260
- const workspace = yield* Effect8.tryPromise({
5962
+ const workflow = Effect10.gen(function* () {
5963
+ if (task.provider === "grok-bot") {
5964
+ return yield* Effect10.tryPromise({
5965
+ try: (signal) => runGrokBot(configuration.grokGateway, task, signal),
5966
+ catch: (cause) => new ProviderRunError({
5967
+ provider: task.provider,
5968
+ message: cause instanceof Error ? cause.message : String(cause)
5969
+ })
5970
+ });
5971
+ }
5972
+ const workspace = yield* Effect10.tryPromise({
5261
5973
  try: () => openAllowedWorkspace(configuration, task.workspace),
5262
5974
  catch: (cause) => new ProviderRunError({
5263
5975
  provider: task.provider,
@@ -5268,17 +5980,18 @@ var execute = (configuration, state, task) => {
5268
5980
  return yield* run({
5269
5981
  provider: task.provider,
5270
5982
  threadId: task.threadId,
5983
+ taskId: task.id,
5271
5984
  prompt: promptFor(task, configuration.name),
5272
5985
  cwd: workspace.cwd,
5273
5986
  beforeSpawn: workspace.validate,
5274
5987
  ...sessionId === undefined ? {} : { sessionId },
5275
5988
  ...configuration.model === undefined ? {} : { model: configuration.model },
5276
5989
  ...configuration.sandbox === undefined ? {} : { sandbox: configuration.sandbox }
5277
- }).pipe(Effect8.ensuring(Effect8.promise(() => workspace.close().catch(() => {
5990
+ }).pipe(Effect10.ensuring(Effect10.promise(() => workspace.close().catch(() => {
5278
5991
  return;
5279
5992
  }))));
5280
5993
  });
5281
- Effect8.runPromise(workflow, { signal: controller.signal }).then((result) => {
5994
+ Effect10.runPromise(workflow, { signal: controller.signal }).then((result) => {
5282
5995
  if (result.sessionId !== undefined) {
5283
5996
  state.sessions.set(sessionKey(task), result.sessionId);
5284
5997
  }
@@ -5298,15 +6011,7 @@ var execute = (configuration, state, task) => {
5298
6011
  }));
5299
6012
  }).finally(() => {
5300
6013
  state.tasks.delete(task.id);
5301
- const operation = state.operationQueue.shift();
5302
- if (operation !== undefined) {
5303
- executeOperation(configuration, state, operation);
5304
- return;
5305
- }
5306
- const next = state.queue.shift();
5307
- if (next !== undefined && state.operation === undefined) {
5308
- execute(configuration, state, next);
5309
- }
6014
+ drain(configuration, state);
5310
6015
  });
5311
6016
  };
5312
6017
  var executeOperation = (configuration, state, operation) => {
@@ -5331,15 +6036,7 @@ var executeOperation = (configuration, state, operation) => {
5331
6036
  }));
5332
6037
  }).finally(() => {
5333
6038
  state.operation = undefined;
5334
- const pending = state.operationQueue.shift();
5335
- if (pending !== undefined) {
5336
- executeOperation(configuration, state, pending);
5337
- return;
5338
- }
5339
- const next = state.queue.shift();
5340
- if (next !== undefined && state.tasks.size === 0) {
5341
- execute(configuration, state, next);
5342
- }
6039
+ drain(configuration, state);
5343
6040
  });
5344
6041
  };
5345
6042
  var scheduleOperation = (configuration, state, operation) => {
@@ -5370,7 +6067,7 @@ var schedule = (configuration, state, task) => {
5370
6067
  if (task.providerSessionId !== undefined) {
5371
6068
  state.sessions.set(sessionKey(task), task.providerSessionId);
5372
6069
  }
5373
- if (state.tasks.size > 0 || state.operation !== undefined) {
6070
+ if ([...state.tasks.values()].some(({ task: active }) => taskSlot(active) === taskSlot(task)) || state.operation !== undefined) {
5374
6071
  if (state.queue.length >= 100) {
5375
6072
  state.socket?.close(4008, "Task queue limit reached");
5376
6073
  return;
@@ -5383,7 +6080,9 @@ var schedule = (configuration, state, task) => {
5383
6080
  var cancel = (state, taskId) => {
5384
6081
  const running = state.tasks.get(taskId);
5385
6082
  if (running !== undefined) {
5386
- running.abort();
6083
+ if (running.task.provider !== "grok-bot") {
6084
+ running.controller.abort();
6085
+ }
5387
6086
  return;
5388
6087
  }
5389
6088
  const index = state.queue.findIndex((task) => task.id === taskId);
@@ -5393,18 +6092,39 @@ var cancel = (state, taskId) => {
5393
6092
  remember(state, taskId);
5394
6093
  sendTerminal(state, taskId, SocketEvent.make({ _tag: "TaskCancelled", taskId }));
5395
6094
  };
5396
- var connect = (configuration, state) => Effect8.tryPromise({
6095
+ var connect = (configuration, state) => Effect10.tryPromise({
5397
6096
  try: (signal) => new Promise((complete) => {
5398
6097
  const socket = new WebSocket2(socketUrl(configuration), {
5399
6098
  maxPayload: maxSocketPayloadBytes,
5400
- perMessageDeflate: false
6099
+ perMessageDeflate: false,
6100
+ allowSynchronousEvents: false
5401
6101
  });
6102
+ let bots = [];
6103
+ let refreshing = false;
6104
+ const refreshBots = async () => {
6105
+ if (refreshing || configuration.grokGateway === undefined || configuration.providers !== undefined && !configuration.providers.includes("grok-bot")) {
6106
+ return;
6107
+ }
6108
+ refreshing = true;
6109
+ bots = await discoverGrokBots(configuration.grokGateway).catch(() => []);
6110
+ refreshing = false;
6111
+ if (!closed) {
6112
+ send(state, SocketEvent.make({
6113
+ _tag: "DeviceHeartbeat",
6114
+ deviceId: configuration.id,
6115
+ status: state.tasks.size > 0 || state.operation !== undefined ? "busy" : "online",
6116
+ bots
6117
+ }));
6118
+ }
6119
+ };
5402
6120
  const heartbeat = setInterval(() => {
5403
6121
  send(state, SocketEvent.make({
5404
6122
  _tag: "DeviceHeartbeat",
5405
6123
  deviceId: configuration.id,
5406
- status: state.tasks.size > 0 || state.operation !== undefined ? "busy" : "online"
6124
+ status: state.tasks.size > 0 || state.operation !== undefined ? "busy" : "online",
6125
+ ...configuration.grokGateway === undefined ? {} : { bots }
5407
6126
  }));
6127
+ refreshBots();
5408
6128
  }, 15000);
5409
6129
  let closed = false;
5410
6130
  let queuedMessages = 0;
@@ -5436,16 +6156,16 @@ var connect = (configuration, state) => Effect8.tryPromise({
5436
6156
  if (closed) {
5437
6157
  return;
5438
6158
  }
5439
- const text2 = Buffer.isBuffer(message) ? message.toString("utf8") : message instanceof ArrayBuffer ? Buffer.from(message).toString("utf8") : Array.isArray(message) ? Buffer.concat(message).toString("utf8") : Buffer.from(message).toString("utf8");
5440
- const event = await Effect8.runPromise(Effect8.try({
5441
- try: () => JSON.parse(text2),
6159
+ const text3 = Buffer.isBuffer(message) ? message.toString("utf8") : message instanceof ArrayBuffer ? Buffer.from(message).toString("utf8") : Array.isArray(message) ? Buffer.concat(message).toString("utf8") : Buffer.from(message).toString("utf8");
6160
+ const event = await Effect10.runPromise(Effect10.try({
6161
+ try: () => JSON.parse(text3),
5442
6162
  catch: () => new DeviceConnectionError({ message: "Relay sent invalid JSON" })
5443
- }).pipe(Effect8.flatMap(decodeSocketEvent)));
6163
+ }).pipe(Effect10.flatMap(decodeSocketEvent)));
5444
6164
  if (event._tag === "Connected") {
5445
6165
  state.socket = socket;
5446
6166
  socket.send(JSON.stringify(SocketEvent.make({
5447
6167
  _tag: "DeviceHello",
5448
- device: describeDevice(configuration, state.tasks.size > 0 || state.operation !== undefined ? "busy" : "online")
6168
+ device: describeDevice(configuration, state.tasks.size > 0 || state.operation !== undefined ? "busy" : "online", bots)
5449
6169
  })));
5450
6170
  for (const payload of state.terminal.values()) {
5451
6171
  socket.send(payload);
@@ -5453,6 +6173,7 @@ var connect = (configuration, state) => Effect8.tryPromise({
5453
6173
  for (const payload of state.operationTerminal.values()) {
5454
6174
  socket.send(payload);
5455
6175
  }
6176
+ refreshBots();
5456
6177
  return;
5457
6178
  }
5458
6179
  if (event._tag === "TaskAssigned") {
@@ -5465,6 +6186,9 @@ var connect = (configuration, state) => Effect8.tryPromise({
5465
6186
  }
5466
6187
  if (event._tag === "TaskSettled") {
5467
6188
  state.terminal.delete(event.taskId);
6189
+ cleanupBotReply(event.taskId).catch(() => {
6190
+ console.error(`Could not remove local bot reply for settled task ${event.taskId}`);
6191
+ });
5468
6192
  return;
5469
6193
  }
5470
6194
  if (event._tag === "OperationAssigned") {
@@ -5505,9 +6229,9 @@ var runDaemon = (configuration) => {
5505
6229
  operationTerminal: new Map,
5506
6230
  completedOperations: new Set
5507
6231
  };
5508
- return connect(configuration, state).pipe(Effect8.repeat({ schedule: Schedule2.spaced("2 seconds") }), Effect8.ensuring(Effect8.sync(() => {
6232
+ return connect(configuration, state).pipe(Effect10.repeat({ schedule: Schedule3.spaced("2 seconds") }), Effect10.ensuring(Effect10.sync(() => {
5509
6233
  state.socket?.close();
5510
- for (const controller of state.tasks.values()) {
6234
+ for (const { controller } of state.tasks.values()) {
5511
6235
  controller.abort();
5512
6236
  }
5513
6237
  })));
@@ -5517,7 +6241,7 @@ var runDaemon = (configuration) => {
5517
6241
  init_src();
5518
6242
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5519
6243
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5520
- import { Effect as Effect9 } from "effect";
6244
+ import { Effect as Effect11 } from "effect";
5521
6245
  import * as z from "zod/v4";
5522
6246
  var output = (value) => ({
5523
6247
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
@@ -5532,41 +6256,58 @@ var runMcp = async (configuration) => {
5532
6256
  title: "List Cohall devices",
5533
6257
  description: "List devices, their availability, local providers, capabilities, and allowed workspaces.",
5534
6258
  inputSchema: {}
5535
- }, async () => output(await Effect9.runPromise(client2.devices())));
6259
+ }, async () => output(await Effect11.runPromise(client2.devices())));
6260
+ server.registerTool("list_bots", {
6261
+ title: "List Cohall bots",
6262
+ description: "List named Grok Bots on paired devices, their host availability, and unambiguous targets for delegate. Bot names can also be used when unique.",
6263
+ inputSchema: {}
6264
+ }, async () => output(listBots(await Effect11.runPromise(client2.devices()))));
5536
6265
  server.registerTool("delegate", {
5537
- title: "Delegate work to another device",
5538
- description: "Send focused work to another Cohall device. When the request depends on the current conversation, distill its motivation, relevant facts, prior findings, constraints, and desired decision into context; Cohall cannot read the host transcript. Never forward unrelated transcript content. Reuse thread_id for related follow-ups.",
6266
+ title: "Send work to a device or bot",
6267
+ description: "Send focused work to a Cohall device or named Grok Bot. When the request depends on the current conversation, distill its motivation, relevant facts, prior findings, constraints, and desired decision into context; Cohall cannot read the host transcript. Never forward unrelated transcript content. Reuse thread_id for related follow-ups. Grok Bots use their existing conversation; a Cohall thread does not isolate it.",
5539
6268
  inputSchema: {
5540
6269
  prompt: z.string().min(1).max(131072),
5541
- target: z.string().optional().describe("Device name, @name, hostname, or ID"),
5542
- provider: z.enum(["codex", "claude-code", "opencode"]).default("codex"),
6270
+ target: z.string().optional().describe("Device name, hostname or ID; @BotName; or @device/BotName. Use list_bots targets to disambiguate."),
6271
+ provider: z.enum(Provider.literals).optional().describe("Inferred as grok-bot for a bot target; otherwise defaults to codex."),
5543
6272
  context: z.string().max(131072).optional().describe("Distilled context the target needs: why the user is asking, relevant facts and prior findings, constraints, and the intended decision. The calling agent must supply this when the prompt depends on its conversation; omit it only for a self-contained task."),
5544
6273
  thread_id: z.string().uuid().optional(),
5545
- workspace: z.string().max(4096).optional(),
6274
+ parent_task_id: z.string().uuid().optional().describe("The task delegating this work. Inherited from COHALL_TASK_ID when present."),
6275
+ workspace: z.string().max(4096).optional().describe("Coding provider workspace. Omit for named Grok Bots."),
5546
6276
  wait: z.boolean().default(true),
5547
6277
  timeout_seconds: z.number().int().min(5).max(86400).default(900)
5548
6278
  }
5549
- }, async ({ prompt, target, provider, context, thread_id, workspace, wait, timeout_seconds }) => {
5550
- const task = await Effect9.runPromise(createDelegation(client2, configuration, {
6279
+ }, async ({
6280
+ prompt,
6281
+ target,
6282
+ provider,
6283
+ context,
6284
+ thread_id,
6285
+ parent_task_id,
6286
+ workspace,
6287
+ wait,
6288
+ timeout_seconds
6289
+ }) => {
6290
+ const task = await Effect11.runPromise(createDelegation(client2, configuration, {
5551
6291
  prompt,
5552
- provider,
6292
+ ...provider === undefined ? {} : { provider },
5553
6293
  ...target === undefined ? {} : { target },
5554
6294
  ...context === undefined ? {} : { context },
5555
6295
  ...thread_id === undefined ? {} : { threadId: ThreadId.make(thread_id) },
6296
+ ...parent_task_id === undefined ? {} : { parentTaskId: TaskId.make(parent_task_id) },
5556
6297
  ...workspace === undefined ? {} : { workspace }
5557
6298
  }));
5558
- return output(taskResult(wait ? await Effect9.runPromise(waitForTask(client2, task, timeout_seconds)) : task));
6299
+ return output(taskResult(wait ? await Effect11.runPromise(waitForTask(client2, task, timeout_seconds)) : task));
5559
6300
  });
5560
6301
  server.registerTool("task_status", {
5561
6302
  title: "Get delegated task status",
5562
6303
  description: "Read the current state and result of a Cohall task.",
5563
6304
  inputSchema: { task_id: z.string().uuid() }
5564
- }, async ({ task_id }) => output(taskResult(await Effect9.runPromise(client2.getTask(TaskId.make(task_id))))));
6305
+ }, async ({ task_id }) => output(taskResult(await Effect11.runPromise(client2.getTask(TaskId.make(task_id))))));
5565
6306
  server.registerTool("task_trace", {
5566
6307
  title: "Trace a delegated task",
5567
6308
  description: "Read the redacted relay and device lifecycle for troubleshooting a Cohall task.",
5568
6309
  inputSchema: { task_id: z.string().uuid() }
5569
- }, async ({ task_id }) => output(await Effect9.runPromise(client2.traceTask(TaskId.make(task_id)))));
6310
+ }, async ({ task_id }) => output(await Effect11.runPromise(client2.traceTask(TaskId.make(task_id)))));
5570
6311
  server.registerTool("wait_task", {
5571
6312
  title: "Wait for delegated work",
5572
6313
  description: "Wait for a Cohall task to finish and return its final result.",
@@ -5575,19 +6316,19 @@ var runMcp = async (configuration) => {
5575
6316
  timeout_seconds: z.number().int().min(5).max(86400).default(900)
5576
6317
  }
5577
6318
  }, async ({ task_id, timeout_seconds }) => {
5578
- const task = await Effect9.runPromise(client2.getTask(TaskId.make(task_id)));
5579
- return output(taskResult(await Effect9.runPromise(waitForTask(client2, task, timeout_seconds))));
6319
+ const task = await Effect11.runPromise(client2.getTask(TaskId.make(task_id)));
6320
+ return output(taskResult(await Effect11.runPromise(waitForTask(client2, task, timeout_seconds))));
5580
6321
  });
5581
6322
  server.registerTool("cancel_task", {
5582
6323
  title: "Cancel delegated work",
5583
- description: "Request cancellation and return its acknowledged or pending state.",
6324
+ description: "Request cancellation of coding work or a queued Bot task. Active Grok Bot tasks must be stopped in Grok Bot.",
5584
6325
  inputSchema: { task_id: z.string().uuid() }
5585
- }, async ({ task_id }) => output(taskResult(await Effect9.runPromise(client2.cancelTask(TaskId.make(task_id))))));
6326
+ }, async ({ task_id }) => output(taskResult(await Effect11.runPromise(client2.cancelTask(TaskId.make(task_id))))));
5586
6327
  server.registerTool("thread_context", {
5587
6328
  title: "Read a Cohall thread",
5588
6329
  description: "Read shared task prompts and final results for a cross-device thread.",
5589
6330
  inputSchema: { thread_id: z.string().uuid() }
5590
- }, async ({ thread_id }) => output(await Effect9.runPromise(threadContext(client2, ThreadId.make(thread_id)))));
6331
+ }, async ({ thread_id }) => output(await Effect11.runPromise(threadContext(client2, ThreadId.make(thread_id)))));
5591
6332
  await server.connect(new StdioServerTransport);
5592
6333
  };
5593
6334
 
@@ -5616,12 +6357,12 @@ var main = async () => {
5616
6357
  return;
5617
6358
  }
5618
6359
  if (command === "device") {
5619
- const configuration = await Effect13.runPromise(loadDeviceConfiguration);
5620
- await Effect13.runPromise(runDaemon(configuration));
6360
+ const configuration = await Effect15.runPromise(loadDeviceConfiguration);
6361
+ await Effect15.runPromise(runDaemon(configuration));
5621
6362
  return;
5622
6363
  }
5623
6364
  if (command === "mcp") {
5624
- const configuration = await Effect13.runPromise(loadClientConfiguration);
6365
+ const configuration = await Effect15.runPromise(loadClientConfiguration);
5625
6366
  await runMcp(configuration);
5626
6367
  return;
5627
6368
  }
@@ -5632,4 +6373,4 @@ await main().catch((cause) => {
5632
6373
  process.exitCode = 1;
5633
6374
  });
5634
6375
 
5635
- //# debugId=28F1D5ED59BE527664756E2164756E21
6376
+ //# debugId=F147BDC9F8E0C7EE64756E2164756E21