@akshar5/cohall 0.5.5 → 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/CHANGELOG.md +14 -0
- package/README.md +72 -1
- package/bin/cohall.js +1217 -476
- package/bin/cohall.js.map +14 -12
- package/docs/install.md +12 -3
- package/docs/integrations.md +5 -0
- package/package.json +1 -1
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.
|
|
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.5", 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,
|
|
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,
|
|
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,
|
|
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,
|
|
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: {
|
|
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
|
|
298
|
-
import { access as access5, chmod as
|
|
299
|
-
import { homedir as homedir5, platform as
|
|
300
|
-
import { join as
|
|
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 (
|
|
303
|
-
return
|
|
324
|
+
if (platform5() === "win32") {
|
|
325
|
+
return join8(process.env.LOCALAPPDATA ?? join8(homedir5(), "AppData", "Local"), "Cohall");
|
|
304
326
|
}
|
|
305
|
-
if (
|
|
306
|
-
return
|
|
327
|
+
if (platform5() === "darwin") {
|
|
328
|
+
return join8(homedir5(), "Library", "Application Support", "Cohall", "relay");
|
|
307
329
|
}
|
|
308
|
-
return
|
|
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 (
|
|
317
|
-
await
|
|
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 =
|
|
332
|
-
host:
|
|
333
|
-
port:
|
|
334
|
-
token:
|
|
335
|
-
dataDirectory:
|
|
336
|
-
databasePath:
|
|
337
|
-
allowRemote:
|
|
338
|
-
historyTaskLimit:
|
|
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 =
|
|
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 =
|
|
349
|
-
await
|
|
350
|
-
if (
|
|
351
|
-
await
|
|
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(
|
|
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
|
|
380
|
+
return Schema12.decodeUnknownSync(RelayConfiguration)({
|
|
359
381
|
host,
|
|
360
382
|
port,
|
|
361
383
|
token,
|
|
362
384
|
dataDirectory,
|
|
363
|
-
databasePath:
|
|
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
|
|
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
|
-
}),
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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 ?
|
|
645
|
+
return role === undefined ? Effect13.fail(new PersistenceError({
|
|
622
646
|
operation: "RelayStore.decodeAuthSession",
|
|
623
647
|
message: `Session ${row.id} has no role`
|
|
624
|
-
})) :
|
|
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 =
|
|
717
|
-
const row = yield*
|
|
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*
|
|
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 =
|
|
755
|
+
const traceTask = Effect13.fn("RelayStore.traceTask")(function* (taskId) {
|
|
730
756
|
const task = yield* getTask(taskId);
|
|
731
|
-
const [deviceRow, eventRows] = yield*
|
|
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*
|
|
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*
|
|
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 =
|
|
770
|
-
const rows = yield*
|
|
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*
|
|
801
|
+
return yield* Effect13.forEach(rows, deviceFromRow);
|
|
775
802
|
});
|
|
776
|
-
const usage =
|
|
777
|
-
const rows = yield*
|
|
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 =
|
|
792
|
-
const provider =
|
|
793
|
-
const deviceId =
|
|
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 =
|
|
824
|
-
const row = yield*
|
|
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*
|
|
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 =
|
|
837
|
-
const rows = yield*
|
|
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*
|
|
868
|
+
return yield* Effect13.forEach(rows, operationFromRow);
|
|
842
869
|
});
|
|
843
|
-
const createUpgradeOperations =
|
|
870
|
+
const createUpgradeOperations = Effect13.fn("RelayStore.createUpgradeOperations")(function* (input) {
|
|
844
871
|
const timestamp = now();
|
|
845
|
-
const operations = yield*
|
|
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 =
|
|
886
|
-
const rows = yield*
|
|
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*
|
|
918
|
+
return yield* Effect13.forEach(rows, operationFromRow);
|
|
892
919
|
});
|
|
893
|
-
const transitionOperation =
|
|
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*
|
|
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 =
|
|
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*
|
|
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 =
|
|
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*
|
|
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(
|
|
947
|
-
const finishOperation = (operationId, deviceId, result) => requireOperationTarget(operationId, deviceId).pipe(
|
|
948
|
-
const failOperation = (operationId, deviceId, error) => requireOperationTarget(operationId, deviceId).pipe(
|
|
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 =
|
|
951
|
-
yield*
|
|
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 =
|
|
958
|
-
const row = yield*
|
|
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 =
|
|
986
|
-
yield*
|
|
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 =
|
|
1005
|
-
yield*
|
|
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,
|
|
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 =
|
|
1049
|
+
const updateDeviceStatus = Effect13.fn("RelayStore.updateDeviceStatus")(function* (deviceId, status, bots) {
|
|
1022
1050
|
const timestamp = now();
|
|
1023
|
-
const result = yield*
|
|
1024
|
-
try: () => db.query("UPDATE devices SET status = ?, last_seen_at =
|
|
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*
|
|
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*
|
|
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 =
|
|
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*
|
|
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 =
|
|
1105
|
-
const threadRow = yield*
|
|
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*
|
|
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*
|
|
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*
|
|
1157
|
+
const [thread, messages, tasks] = yield* Effect13.all([
|
|
1129
1158
|
threadFromRow(threadRow),
|
|
1130
|
-
|
|
1131
|
-
|
|
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 =
|
|
1143
|
-
const rows = yield*
|
|
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*
|
|
1177
|
+
return yield* Effect13.forEach(rows, taskFromRow);
|
|
1149
1178
|
});
|
|
1150
|
-
const transition =
|
|
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*
|
|
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 =
|
|
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*
|
|
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 =
|
|
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*
|
|
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 =
|
|
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*
|
|
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 =
|
|
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 =
|
|
1295
|
+
const requeueTasksFor = Effect13.fn("RelayStore.requeueTasksFor")(function* (deviceId) {
|
|
1259
1296
|
const timestamp = now();
|
|
1260
|
-
yield*
|
|
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 =
|
|
1274
|
-
const row = yield*
|
|
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 =
|
|
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*
|
|
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*
|
|
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 =
|
|
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*
|
|
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 =
|
|
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*
|
|
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 =
|
|
1340
|
-
const row = yield*
|
|
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*
|
|
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 =
|
|
1357
|
-
const rows = yield*
|
|
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*
|
|
1398
|
+
return yield* Effect13.forEach(rows, authSessionFromRow);
|
|
1362
1399
|
});
|
|
1363
|
-
const revokeAuthSession =
|
|
1400
|
+
const revokeAuthSession = Effect13.fn("RelayStore.revokeAuthSession")(function* (sessionId) {
|
|
1364
1401
|
const revokedAt = now();
|
|
1365
|
-
const result = yield*
|
|
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*
|
|
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*
|
|
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(
|
|
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) =>
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
1563
|
+
import { access as access6, chmod as chmod8 } from "node:fs/promises";
|
|
1519
1564
|
import { createServer } from "node:http";
|
|
1520
|
-
import { platform as
|
|
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((
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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) =>
|
|
1668
|
+
}, chooseDevice = (input, sourceDeviceId) => Effect14.gen(function* () {
|
|
1624
1669
|
const store = yield* exports_store.Service;
|
|
1625
|
-
const devices = yield* store.listDevices().pipe(
|
|
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
|
|
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
|
|
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(
|
|
1714
|
+
await run2(Effect14.gen(function* () {
|
|
1657
1715
|
const store = yield* exports_store.Service;
|
|
1658
1716
|
yield* store.recover();
|
|
1659
1717
|
}));
|
|
1660
|
-
if (
|
|
1661
|
-
await
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1718
|
-
const
|
|
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
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
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
|
|
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(
|
|
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(
|
|
1869
|
+
await run2(Effect14.gen(function* () {
|
|
1806
1870
|
const store = yield* exports_store.Service;
|
|
1807
1871
|
yield* store.upsertDevice(device);
|
|
1808
1872
|
}));
|
|
1809
|
-
await
|
|
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(
|
|
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(
|
|
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 =
|
|
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*
|
|
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*
|
|
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(
|
|
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(
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
|
2109
|
-
status:
|
|
2110
|
-
message:
|
|
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
|
|
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) =>
|
|
2192
|
-
const response = yield*
|
|
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*
|
|
2274
|
+
return yield* Effect2.fail(new RelayRequestError({
|
|
2209
2275
|
operation,
|
|
2210
|
-
message: yield*
|
|
2276
|
+
message: yield* Effect2.promise(() => responseMessage(response, operation)),
|
|
2211
2277
|
status: response.status
|
|
2212
2278
|
}));
|
|
2213
2279
|
}
|
|
2214
|
-
const json = yield*
|
|
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(
|
|
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) =>
|
|
2311
|
+
var exchangePairing = (baseUrl, input) => Effect2.gen(function* () {
|
|
2246
2312
|
const operation = "RelayClient.exchangePairing";
|
|
2247
|
-
const response = yield*
|
|
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*
|
|
2326
|
+
return yield* Effect2.fail(new RelayRequestError({
|
|
2261
2327
|
operation,
|
|
2262
|
-
message: yield*
|
|
2328
|
+
message: yield* Effect2.promise(() => responseMessage(response, operation)),
|
|
2263
2329
|
status: response.status
|
|
2264
2330
|
}));
|
|
2265
2331
|
}
|
|
2266
|
-
const json = yield*
|
|
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(
|
|
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
|
|
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;
|
|
@@ -2426,8 +2492,8 @@ var opencodeCommand = (options, promptPath) => [
|
|
|
2426
2492
|
"json",
|
|
2427
2493
|
...options.model === undefined ? [] : ["--model", options.model],
|
|
2428
2494
|
...options.sessionId === undefined ? [] : ["--session", options.sessionId],
|
|
2429
|
-
|
|
2430
|
-
|
|
2495
|
+
"Complete the task described in the attached Cohall prompt file.",
|
|
2496
|
+
`--file=${promptPath}`
|
|
2431
2497
|
];
|
|
2432
2498
|
var prepareCommand = async (options) => {
|
|
2433
2499
|
switch (options.provider) {
|
|
@@ -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) =>
|
|
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
|
|
2739
|
-
import { access as access4, mkdir as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
2763
|
-
|
|
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
|
-
|
|
2809
|
-
use Codex
|
|
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
|
|
2826
|
-
|
|
2827
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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) =>
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
3164
|
-
const
|
|
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 =
|
|
3324
|
+
var createDelegation = Effect5.fn("Cohall.createDelegation")(function* (client, configuration, options) {
|
|
3177
3325
|
const devices = yield* client.devices();
|
|
3178
|
-
|
|
3179
|
-
if (options.target !== undefined && matches.length !== 1) {
|
|
3326
|
+
if (devices.length === 0) {
|
|
3180
3327
|
return yield* new DeviceSelectionError({
|
|
3181
|
-
message:
|
|
3182
|
-
devices:
|
|
3328
|
+
message: "No Cohall devices are registered",
|
|
3329
|
+
devices: []
|
|
3183
3330
|
});
|
|
3184
3331
|
}
|
|
3185
|
-
|
|
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:
|
|
3349
|
+
message: `A bot target requires provider grok-bot, not ${options.provider}`,
|
|
3188
3350
|
devices: []
|
|
3189
3351
|
});
|
|
3190
3352
|
}
|
|
3191
|
-
|
|
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.
|
|
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
|
-
...
|
|
3369
|
+
...provider === undefined ? {} : { provider },
|
|
3370
|
+
...parentTaskId === undefined ? {} : { parentTaskId }
|
|
3200
3371
|
});
|
|
3201
3372
|
});
|
|
3202
|
-
var waitForTask =
|
|
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*
|
|
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 =
|
|
3389
|
+
var followTaskTrace = Effect5.fn("Cohall.followTaskTrace")(function* (client, taskId, onTrace) {
|
|
3219
3390
|
let revision;
|
|
3220
|
-
const poll = client.traceTask(taskId).pipe(
|
|
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
|
|
3395
|
+
return Effect5.succeed(undefined);
|
|
3225
3396
|
}
|
|
3226
3397
|
revision = nextRevision;
|
|
3227
|
-
return
|
|
3398
|
+
return Effect5.sync(() => onTrace(trace));
|
|
3228
3399
|
}));
|
|
3229
|
-
return yield* poll.pipe(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3997
|
+
chmod as chmod6,
|
|
3387
3998
|
copyFile,
|
|
3388
3999
|
lstat,
|
|
3389
|
-
mkdir as
|
|
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
|
|
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
|
|
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
|
|
4017
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
3407
4018
|
|
|
3408
4019
|
// apps/device/src/upgrade.ts
|
|
3409
|
-
import { Schema as
|
|
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
|
|
3415
|
-
mkdir as
|
|
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
|
|
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 =
|
|
3428
|
-
var RestartReceipt =
|
|
3429
|
-
version:
|
|
3430
|
-
fromVersion:
|
|
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:
|
|
3433
|
-
restartedServices:
|
|
3434
|
-
restartingService:
|
|
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 = (
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
4330
|
+
const directory = dirname3(path);
|
|
3720
4331
|
const temporary = `${path}.${process.pid}.tmp`;
|
|
3721
|
-
await
|
|
4332
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
3722
4333
|
if (operatingSystem() !== "win32") {
|
|
3723
|
-
await
|
|
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(
|
|
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 = () =>
|
|
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("&", "&").replaceAll("<", "<").repl
|
|
|
3905
4516
|
var systemdArgument = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
|
|
3906
4517
|
var deviceServicePlan = (options) => {
|
|
3907
4518
|
if (options.platform === "linux") {
|
|
3908
|
-
const path =
|
|
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 =
|
|
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
|
|
4623
|
+
const platform4 = options.platform ?? process.platform;
|
|
4013
4624
|
const runner = options.runner ?? defaultRunner2;
|
|
4014
|
-
if (
|
|
4015
|
-
const script =
|
|
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:
|
|
4638
|
+
platform: platform4,
|
|
4028
4639
|
entrypoint,
|
|
4029
4640
|
home: options.home ?? homedir3(),
|
|
4030
4641
|
...uid === undefined ? {} : { uid }
|
|
4031
4642
|
});
|
|
4032
|
-
await
|
|
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
|
|
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,
|
|
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
|
|
4655
|
+
const platform4 = options.platform ?? process.platform;
|
|
4045
4656
|
const runner = options.runner ?? defaultRunner2;
|
|
4046
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 =
|
|
4080
|
-
var RelayBackupManifest =
|
|
4081
|
-
formatVersion:
|
|
4082
|
-
cohallVersion:
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
4161
|
-
await
|
|
4771
|
+
await verifyBackupDestinationParent(dirname5(destination));
|
|
4772
|
+
await mkdir5(destination, { mode: 448 });
|
|
4162
4773
|
try {
|
|
4163
|
-
const databasePath =
|
|
4164
|
-
const ownerTokenPath =
|
|
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([
|
|
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(
|
|
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 =
|
|
4205
|
-
const sourceOwnerToken =
|
|
4206
|
-
const sourceManifest =
|
|
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 =
|
|
4222
|
-
await
|
|
4223
|
-
const staging = await mkdtemp2(
|
|
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
|
|
4227
|
-
const databasePath =
|
|
4228
|
-
const ownerTokenPath =
|
|
4229
|
-
const manifestPath =
|
|
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
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
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
|
|
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 =
|
|
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
|
|
4410
|
-
|
|
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 >
|
|
4531
|
-
throw new Error(`${name} file exceeds
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
4592
|
-
claude:
|
|
4593
|
-
opencode:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5405
|
+
print(await Effect9.runPromise(relay2.abandonOperation(Schema10.decodeUnknownSync(OperationId)(arguments_.positionals[1]))));
|
|
4771
5406
|
return;
|
|
4772
5407
|
}
|
|
4773
|
-
print(await
|
|
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_, [
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5645
|
+
print(await Effect9.runPromise(relay2.authSessions()));
|
|
4986
5646
|
return;
|
|
4987
5647
|
}
|
|
4988
5648
|
if (command === "forget") {
|
|
4989
5649
|
allowOptions(arguments_, []);
|
|
4990
|
-
print(await
|
|
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
|
|
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
|
|
5661
|
+
print(await Effect9.runPromise(relay.devices()));
|
|
5002
5662
|
return;
|
|
5003
5663
|
}
|
|
5004
|
-
if (command === "
|
|
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 ??
|
|
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 &&
|
|
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
|
|
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:
|
|
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:
|
|
5713
|
+
...provider === undefined ? {} : { provider: Schema10.decodeUnknownSync(Provider)(provider) }
|
|
5040
5714
|
}));
|
|
5041
|
-
print(taskResult(arguments_.options.has("no-wait") ? task : await
|
|
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 =
|
|
5047
|
-
print(taskResult(await
|
|
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 =
|
|
5726
|
+
const id = Schema10.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
|
|
5053
5727
|
if (arguments_.options.has("follow")) {
|
|
5054
|
-
await
|
|
5728
|
+
await Effect9.runPromise(followTaskTrace(relay, id, printLine));
|
|
5055
5729
|
return;
|
|
5056
5730
|
}
|
|
5057
|
-
print(await
|
|
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 =
|
|
5063
|
-
const task = await
|
|
5064
|
-
print(taskResult(await
|
|
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 =
|
|
5070
|
-
print(taskResult(await
|
|
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 =
|
|
5076
|
-
print(await
|
|
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
|
|
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
|
|
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
|
|
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 ||
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5847
|
+
const operatingSystem2 = platform4();
|
|
5168
5848
|
const flags = constants3.O_RDONLY | (operatingSystem2 === "win32" ? 0 : constants3.O_DIRECTORY | constants3.O_NOFOLLOW);
|
|
5169
|
-
const handle = await
|
|
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 =
|
|
5260
|
-
|
|
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(
|
|
5990
|
+
}).pipe(Effect10.ensuring(Effect10.promise(() => workspace.close().catch(() => {
|
|
5278
5991
|
return;
|
|
5279
5992
|
}))));
|
|
5280
5993
|
});
|
|
5281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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) =>
|
|
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
|
|
5440
|
-
const event = await
|
|
5441
|
-
try: () => JSON.parse(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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: "
|
|
5538
|
-
description: "Send focused work to
|
|
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,
|
|
5542
|
-
provider: z.enum(
|
|
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
|
-
|
|
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 ({
|
|
5550
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5579
|
-
return output(taskResult(await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5620
|
-
await
|
|
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
|
|
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=
|
|
6376
|
+
//# debugId=F147BDC9F8E0C7EE64756E2164756E21
|