@akshar5/cohall 0.7.0 → 0.9.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 +21 -0
- package/README.md +19 -0
- package/bin/cohall.js +239 -26
- package/bin/cohall.js.map +14 -12
- package/docs/install.md +3 -1
- package/docs/integrations.md +33 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.0](https://github.com/AksharP5/cohall/compare/v0.8.0...v0.9.0) (2026-09-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **devices:** show queue depth and oldest wait ([#95](https://github.com/AksharP5/cohall/issues/95)) ([f9a6f94](https://github.com/AksharP5/cohall/commit/f9a6f943b61a22840ce4be73f79a965fb5a193c3))
|
|
9
|
+
* **tasks:** report bounded worker progress ([#97](https://github.com/AksharP5/cohall/issues/97)) ([ced7062](https://github.com/AksharP5/cohall/commit/ced706251800fb4daa47f5ef9fe8e2672c9d445a))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **doctor:** report rejected client credentials ([#92](https://github.com/AksharP5/cohall/issues/92)) ([8744622](https://github.com/AksharP5/cohall/commit/87446220f84753d18b29d670dc5636c3036ac502))
|
|
15
|
+
* **mcp:** warn when upgrades need a connection restart ([#99](https://github.com/AksharP5/cohall/issues/99)) ([106e4a6](https://github.com/AksharP5/cohall/commit/106e4a645d0cf0814195ca8387cc45b2cb630c86))
|
|
16
|
+
|
|
17
|
+
## [0.8.0](https://github.com/AksharP5/cohall/compare/v0.7.0...v0.8.0) (2026-09-27)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **doctor:** verify MCP server exposes tools ([#89](https://github.com/AksharP5/cohall/issues/89)) ([bee2abc](https://github.com/AksharP5/cohall/commit/bee2abcf64bd5715a88c560d07b8515455cd90d1))
|
|
23
|
+
|
|
3
24
|
## [0.7.0](https://github.com/AksharP5/cohall/compare/v0.6.2...v0.7.0) (2026-09-26)
|
|
4
25
|
|
|
5
26
|
|
package/README.md
CHANGED
|
@@ -8,6 +8,18 @@ One npm package provides a durable self-hosted relay, outbound-only device
|
|
|
8
8
|
workers, a CLI, an installable agent skill, an optional MCP server, and local
|
|
9
9
|
Codex, Claude Code, and OpenCode adapters, plus an experimental Grok Bot adapter.
|
|
10
10
|
|
|
11
|
+
https://github.com/user-attachments/assets/7db8ebb2-da77-484f-bba9-703a44f95f4a
|
|
12
|
+
|
|
13
|
+
<details>
|
|
14
|
+
<summary>Video music credits</summary>
|
|
15
|
+
|
|
16
|
+
Music: ["Hang Low" by Rewob (feat. DustyZonda)](https://ccmixter.org/files/rewob/69753),
|
|
17
|
+
including ["Hang Low a capella - Ashes and Dreams - main vox" by DustyZonda (feat. Liv Mircea)](https://ccmixter.org/files/DustyZonda/60053).
|
|
18
|
+
Both licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
|
|
19
|
+
Trimmed and mixed for this video.
|
|
20
|
+
|
|
21
|
+
</details>
|
|
22
|
+
|
|
11
23
|
## How it works
|
|
12
24
|
|
|
13
25
|
```text
|
|
@@ -197,6 +209,10 @@ npx -y @akshar5/cohall wait <task-id> --timeout 1800
|
|
|
197
209
|
npx -y @akshar5/cohall trace <task-id> --follow
|
|
198
210
|
```
|
|
199
211
|
|
|
212
|
+
Task status and traces include the worker's latest progress note when it reports
|
|
213
|
+
one. Workers can publish a brief milestone with `cohall progress --message
|
|
214
|
+
"Running tests"`; the task ID is inherited during delegated work.
|
|
215
|
+
|
|
200
216
|
Completed tasks also appear in the sending client's inbox. This covers queued
|
|
201
217
|
work and tasks that finish after a wait times out:
|
|
202
218
|
|
|
@@ -351,6 +367,9 @@ cohall usage
|
|
|
351
367
|
```
|
|
352
368
|
|
|
353
369
|
`doctor --all` reports connectivity, providers, workspaces, and version drift.
|
|
370
|
+
It and `cohall devices` include each device's current queue count and the
|
|
371
|
+
creation time of its oldest queued task. Assigned, running, cancelling, and
|
|
372
|
+
terminal tasks do not count as queued. Queue fields are absent with older relays.
|
|
354
373
|
`usage` reports retained Cohall task activity by device, status, and provider;
|
|
355
374
|
provider token counts and billing are not available to the relay.
|
|
356
375
|
|
package/bin/cohall.js
CHANGED
|
@@ -17,7 +17,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
17
17
|
|
|
18
18
|
// packages/protocol/src/index.ts
|
|
19
19
|
import { Effect, Schema } from "effect";
|
|
20
|
-
var version = "0.
|
|
20
|
+
var version = "0.9.0", maxSocketPayloadBytes, maxAttachmentBytes, maxTaskAttachments = 2, maxProgressNoteBytes = 1024, bounded = (maxLength) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maxLength)), optionalText = (maxLength) => Schema.String.check(Schema.isMaxLength(maxLength)), boundedArray = (schema, maxLength) => Schema.Array(schema).check(Schema.isMaxLength(maxLength)), count, uuid = (name) => Schema.String.check(Schema.isUUID(4)).pipe(Schema.brand(name)), isoTimestamp = (name) => Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/, {
|
|
21
21
|
expected: "an ISO-8601 UTC timestamp"
|
|
22
22
|
})).pipe(Schema.brand(name)), BotId, Bot, DeviceId, ThreadId, MessageId, TaskId, AuthSessionId, OperationId, Timestamp, Platform, Provider, DeviceStatus, TaskStatus, OperationStatus, TaskTraceEventKind, MessageRole, ConnectionRole, AuthSession, CreatePairingInput, PairingCredential, ExchangePairingInput, SessionCredential, PairingResult, Capability, Workspace, Device, minimumDeviceOperationVersion = "0.5.0", parsedVersion = (value) => {
|
|
23
23
|
const match = /^v?(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
|
|
@@ -56,7 +56,7 @@ var version = "0.7.0", maxSocketPayloadBytes, maxAttachmentBytes, maxTaskAttachm
|
|
|
56
56
|
return "Grok Bots use their own computer context; omit workspace";
|
|
57
57
|
}
|
|
58
58
|
return provider === "grok-bot" === (input.botId !== undefined) ? undefined : "The grok-bot provider requires botId; other providers cannot select a bot";
|
|
59
|
-
}, Task, TaskInboxItem, TaskInbox, 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) => ({
|
|
59
|
+
}, TaskProgressInput, TaskProgress, Task, TaskInboxItem, TaskInbox, 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, decodeTaskProgressInput, decodeExchangePairingInput, decodeCreateTaskInput = (input) => Schema.decodeUnknownEffect(CreateTaskInput)(input).pipe(Effect.map((task) => ({
|
|
60
60
|
...task,
|
|
61
61
|
provider: task.provider ?? (task.botId === undefined ? "codex" : "grok-bot")
|
|
62
62
|
}))), decodeCreateUpgradeOperationsInput, decodeDevice, decodeSocketEvent;
|
|
@@ -149,7 +149,11 @@ var init_src = __esm(() => {
|
|
|
149
149
|
workspaces: boundedArray(Workspace, 64),
|
|
150
150
|
version: bounded(32),
|
|
151
151
|
lastSeenAt: Timestamp,
|
|
152
|
-
connectedAt: Schema.optionalKey(Timestamp)
|
|
152
|
+
connectedAt: Schema.optionalKey(Timestamp),
|
|
153
|
+
queue: Schema.optionalKey(Schema.Struct({
|
|
154
|
+
queued: count,
|
|
155
|
+
oldestQueuedAt: Schema.optionalKey(Timestamp)
|
|
156
|
+
}))
|
|
153
157
|
});
|
|
154
158
|
Thread = Schema.Struct({
|
|
155
159
|
id: ThreadId,
|
|
@@ -180,6 +184,10 @@ var init_src = __esm(() => {
|
|
|
180
184
|
data: Schema.String.check(Schema.isMaxLength(Math.ceil(maxAttachmentBytes / 3) * 4), Schema.makeFilter((value) => value.length > 0 && value.length % 4 === 0 && value.length / 4 * 3 - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0) <= maxAttachmentBytes && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value) ? undefined : "Expected nonempty base64 data"))
|
|
181
185
|
});
|
|
182
186
|
attachments = boundedArray(InputAttachment, maxTaskAttachments).check(Schema.makeFilter((files) => new Set(files.map((file) => file.name.normalize("NFC").toLocaleLowerCase("en-US"))).size === files.length ? undefined : "Attachment names must be unique"));
|
|
187
|
+
TaskProgressInput = Schema.Struct({
|
|
188
|
+
note: bounded(maxProgressNoteBytes).check(Schema.makeFilter((value) => value.trim().length > 0 && new TextEncoder().encode(value).byteLength <= maxProgressNoteBytes ? undefined : `Expected a nonblank progress note of at most ${maxProgressNoteBytes} UTF-8 bytes`)).pipe(Schema.brand("TaskProgressNote"))
|
|
189
|
+
});
|
|
190
|
+
TaskProgress = Schema.Struct({ ...TaskProgressInput.fields, at: Timestamp });
|
|
183
191
|
Task = Schema.Struct({
|
|
184
192
|
id: TaskId,
|
|
185
193
|
threadId: ThreadId,
|
|
@@ -196,6 +204,7 @@ var init_src = __esm(() => {
|
|
|
196
204
|
result: Schema.optionalKey(optionalText(131072)),
|
|
197
205
|
inputAttachmentNames: Schema.optionalKey(boundedArray(AttachmentName, maxTaskAttachments)),
|
|
198
206
|
error: Schema.optionalKey(optionalText(16384)),
|
|
207
|
+
progress: Schema.optionalKey(TaskProgress),
|
|
199
208
|
createdAt: Timestamp,
|
|
200
209
|
updatedAt: Timestamp,
|
|
201
210
|
startedAt: Schema.optionalKey(Timestamp),
|
|
@@ -284,7 +293,8 @@ var init_src = __esm(() => {
|
|
|
284
293
|
completedAt: Schema.optionalKey(Timestamp),
|
|
285
294
|
events: boundedArray(TaskTraceEvent, 100),
|
|
286
295
|
truncated: Schema.Boolean,
|
|
287
|
-
error: Schema.optionalKey(optionalText(16384))
|
|
296
|
+
error: Schema.optionalKey(optionalText(16384)),
|
|
297
|
+
progress: Schema.optionalKey(TaskProgress)
|
|
288
298
|
}).check(Schema.makeFilter(validBotTarget));
|
|
289
299
|
ThreadContext = Schema.Struct({
|
|
290
300
|
thread: Thread,
|
|
@@ -345,6 +355,7 @@ var init_src = __esm(() => {
|
|
|
345
355
|
"cancelled"
|
|
346
356
|
]);
|
|
347
357
|
decodeCreatePairingInput = Schema.decodeUnknownEffect(CreatePairingInput);
|
|
358
|
+
decodeTaskProgressInput = Schema.decodeUnknownEffect(TaskProgressInput);
|
|
348
359
|
decodeExchangePairingInput = Schema.decodeUnknownEffect(ExchangePairingInput);
|
|
349
360
|
decodeCreateUpgradeOperationsInput = Schema.decodeUnknownEffect(CreateUpgradeOperationsInput);
|
|
350
361
|
decodeDevice = Schema.decodeUnknownEffect(Device);
|
|
@@ -596,13 +607,14 @@ var exports_store = {};
|
|
|
596
607
|
__export(exports_store, {
|
|
597
608
|
layerFromDatabase: () => layerFromDatabase,
|
|
598
609
|
layer: () => layer2,
|
|
610
|
+
TaskProgressError: () => TaskProgressError,
|
|
599
611
|
Service: () => Service2,
|
|
600
612
|
RelayStore: () => exports_store,
|
|
601
613
|
PersistenceError: () => PersistenceError
|
|
602
614
|
});
|
|
603
615
|
import { Context as Context2, Effect as Effect14, Layer as Layer2, Schema as Schema15 } from "effect";
|
|
604
616
|
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
605
|
-
var PersistenceError, Service2, operationError = (operation) => (cause) => new PersistenceError({
|
|
617
|
+
var PersistenceError, TaskProgressError, Service2, operationError = (operation) => (cause) => new PersistenceError({
|
|
606
618
|
operation,
|
|
607
619
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
608
620
|
}), decode2 = (operation, schema, value) => Schema15.decodeUnknownEffect(schema)(value).pipe(Effect14.mapError(operationError(operation))), threadFromRow = (row) => decode2("RelayStore.decodeThread", Thread, {
|
|
@@ -633,7 +645,13 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
633
645
|
workspaces: JSON.parse(row.workspaces_json),
|
|
634
646
|
version: row.version,
|
|
635
647
|
lastSeenAt: row.last_seen_at,
|
|
636
|
-
...row.connected_at === null ? {} : { connectedAt: row.connected_at }
|
|
648
|
+
...row.connected_at === null ? {} : { connectedAt: row.connected_at },
|
|
649
|
+
...row.queued_tasks === undefined ? {} : {
|
|
650
|
+
queue: {
|
|
651
|
+
queued: row.queued_tasks,
|
|
652
|
+
...row.oldest_queued_at === undefined || row.oldest_queued_at === null ? {} : { oldestQueuedAt: row.oldest_queued_at }
|
|
653
|
+
}
|
|
654
|
+
}
|
|
637
655
|
}), taskFromRow = (db, row) => Effect14.try({
|
|
638
656
|
try: () => db.query("SELECT name FROM task_attachments WHERE task_id = ? AND direction = 'input' ORDER BY name").all(row.id),
|
|
639
657
|
catch: operationError("RelayStore.taskAttachments")
|
|
@@ -655,6 +673,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
655
673
|
...row.result === null ? {} : { result: row.result },
|
|
656
674
|
...attachments2.length === 0 ? {} : { inputAttachmentNames: attachments2.map((item) => item.name) },
|
|
657
675
|
...row.error === null ? {} : { error: row.error },
|
|
676
|
+
...row.progress_note === null ? {} : { progress: { note: row.progress_note, at: row.progress_at } },
|
|
658
677
|
...row.started_at === null ? {} : { startedAt: row.started_at },
|
|
659
678
|
...row.completed_at === null ? {} : { completedAt: row.completed_at }
|
|
660
679
|
}))), inboxItemFromRow = (row) => decode2("RelayStore.decodeInboxItem", TaskInboxItem, {
|
|
@@ -902,12 +921,18 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
902
921
|
...task.workspace === undefined ? {} : { workspace: task.workspace },
|
|
903
922
|
...task.startedAt === undefined ? {} : { startedAt: task.startedAt },
|
|
904
923
|
...task.completedAt === undefined ? {} : { completedAt: task.completedAt },
|
|
905
|
-
...task.error === undefined ? {} : { error: task.error }
|
|
924
|
+
...task.error === undefined ? {} : { error: task.error },
|
|
925
|
+
...task.progress === undefined ? {} : { progress: task.progress }
|
|
906
926
|
});
|
|
907
927
|
});
|
|
908
928
|
const listDevices = Effect14.fn("RelayStore.listDevices")(function* () {
|
|
909
929
|
const rows = yield* Effect14.try({
|
|
910
|
-
try: () => db.query(
|
|
930
|
+
try: () => db.query(`SELECT devices.*, COUNT(tasks.id) AS queued_tasks,
|
|
931
|
+
MIN(tasks.created_at) AS oldest_queued_at
|
|
932
|
+
FROM devices LEFT JOIN tasks
|
|
933
|
+
ON tasks.target_device_id = devices.id AND tasks.status = 'queued'
|
|
934
|
+
WHERE devices.forgotten_at IS NULL
|
|
935
|
+
GROUP BY devices.id ORDER BY devices.name COLLATE NOCASE, devices.id`).all(),
|
|
911
936
|
catch: operationError("RelayStore.listDevices")
|
|
912
937
|
});
|
|
913
938
|
return yield* Effect14.forEach(rows, deviceFromRow);
|
|
@@ -1127,7 +1152,8 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
1127
1152
|
const timestamp = now();
|
|
1128
1153
|
const interrupted = db.query("SELECT id FROM tasks WHERE status IN ('assigned', 'running')").all();
|
|
1129
1154
|
db.query("UPDATE devices SET status = 'offline'").run();
|
|
1130
|
-
db.query(
|
|
1155
|
+
db.query(`UPDATE tasks SET status = 'queued', updated_at = ?, progress_note = NULL, progress_at = NULL
|
|
1156
|
+
WHERE status IN ('assigned', 'running')`).run(timestamp);
|
|
1131
1157
|
db.query("UPDATE device_operations SET status = 'queued', updated_at = ? WHERE status IN ('assigned', 'running')").run(timestamp);
|
|
1132
1158
|
for (const task of interrupted) {
|
|
1133
1159
|
recordTaskTraceEvent(TaskId.make(task.id), "requeued", "Relay restarted before the task completed", timestamp);
|
|
@@ -1371,6 +1397,28 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
1371
1397
|
}
|
|
1372
1398
|
return task;
|
|
1373
1399
|
});
|
|
1400
|
+
const reportTaskProgress = Effect14.fn("RelayStore.reportTaskProgress")(function* (taskId, deviceId, note) {
|
|
1401
|
+
const task = yield* getTask(taskId);
|
|
1402
|
+
if (task.targetDeviceId !== deviceId) {
|
|
1403
|
+
return yield* new TaskProgressError({
|
|
1404
|
+
status: 403,
|
|
1405
|
+
message: "Only the target device can report progress"
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
const progress = TaskProgress.make({ note, at: now() });
|
|
1409
|
+
const changed = yield* Effect14.try({
|
|
1410
|
+
try: () => db.query(`UPDATE tasks SET progress_note = ?, progress_at = ?, updated_at = ?
|
|
1411
|
+
WHERE id = ? AND target_device_id = ? AND status = 'running'`).run(note, progress.at, progress.at, taskId, deviceId).changes,
|
|
1412
|
+
catch: operationError("RelayStore.reportTaskProgress")
|
|
1413
|
+
});
|
|
1414
|
+
if (changed !== 1) {
|
|
1415
|
+
return yield* new TaskProgressError({
|
|
1416
|
+
status: 409,
|
|
1417
|
+
message: "Task must be running to report progress"
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
return progress;
|
|
1421
|
+
});
|
|
1374
1422
|
const terminal = Effect14.fn("RelayStore.terminal")(function* (taskId, deviceId, status, result, error, providerSessionId, attachments2) {
|
|
1375
1423
|
const current = yield* requireTarget(taskId, deviceId);
|
|
1376
1424
|
if (["completed", "failed", "cancelled"].includes(current.status)) {
|
|
@@ -1380,7 +1428,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
1380
1428
|
yield* Effect14.try({
|
|
1381
1429
|
try: () => db.transaction(() => {
|
|
1382
1430
|
const updated = db.query(`UPDATE tasks SET status = ?, result = ?, error = ?, provider_session_id = ?,
|
|
1383
|
-
completed_at = ?, updated_at =
|
|
1431
|
+
completed_at = ?, updated_at = ?, progress_note = NULL, progress_at = NULL WHERE id = ?
|
|
1384
1432
|
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);
|
|
1385
1433
|
if (updated.changes !== 1) {
|
|
1386
1434
|
return;
|
|
@@ -1449,7 +1497,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
1449
1497
|
try: () => db.transaction(() => {
|
|
1450
1498
|
const interrupted = db.query(`SELECT id FROM tasks WHERE target_device_id = ?
|
|
1451
1499
|
AND status IN ('assigned', 'running')`).all(deviceId);
|
|
1452
|
-
db.query(`UPDATE tasks SET status = 'queued', updated_at =
|
|
1500
|
+
db.query(`UPDATE tasks SET status = 'queued', updated_at = ?, progress_note = NULL, progress_at = NULL
|
|
1453
1501
|
WHERE target_device_id = ? AND status IN ('assigned', 'running')`).run(timestamp, deviceId);
|
|
1454
1502
|
for (const task of interrupted) {
|
|
1455
1503
|
recordTaskTraceEvent(TaskId.make(task.id), "requeued", "Target device disconnected before task completion", timestamp);
|
|
@@ -1579,6 +1627,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
1579
1627
|
markDeviceOffline: (deviceId) => updateDeviceStatus(deviceId, "offline"),
|
|
1580
1628
|
createDelegation: createDelegation2,
|
|
1581
1629
|
getTask,
|
|
1630
|
+
reportTaskProgress,
|
|
1582
1631
|
listAttachments,
|
|
1583
1632
|
readAttachment,
|
|
1584
1633
|
inboxFor,
|
|
@@ -1650,7 +1699,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
1650
1699
|
target_device_id TEXT NOT NULL REFERENCES devices(id), parent_task_id TEXT,
|
|
1651
1700
|
workspace TEXT, provider_session_id TEXT, result TEXT, error TEXT,
|
|
1652
1701
|
created_at TEXT NOT NULL, updated_at TEXT NOT NULL, started_at TEXT, completed_at TEXT,
|
|
1653
|
-
dispatched_at TEXT
|
|
1702
|
+
dispatched_at TEXT, progress_note TEXT, progress_at TEXT
|
|
1654
1703
|
);
|
|
1655
1704
|
CREATE INDEX IF NOT EXISTS tasks_thread_created ON tasks(thread_id, created_at);
|
|
1656
1705
|
CREATE INDEX IF NOT EXISTS tasks_target_status ON tasks(target_device_id, status);
|
|
@@ -1701,6 +1750,12 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
1701
1750
|
db.exec("ALTER TABLE devices ADD COLUMN bots_json TEXT");
|
|
1702
1751
|
}
|
|
1703
1752
|
const taskColumns = db.query("PRAGMA table_info(tasks)").all();
|
|
1753
|
+
if (!taskColumns.some((column) => column.name === "progress_note")) {
|
|
1754
|
+
db.exec("ALTER TABLE tasks ADD COLUMN progress_note TEXT");
|
|
1755
|
+
}
|
|
1756
|
+
if (!taskColumns.some((column) => column.name === "progress_at")) {
|
|
1757
|
+
db.exec("ALTER TABLE tasks ADD COLUMN progress_at TEXT");
|
|
1758
|
+
}
|
|
1704
1759
|
if (!taskColumns.some((column) => column.name === "bot_id")) {
|
|
1705
1760
|
db.exec("ALTER TABLE tasks ADD COLUMN bot_id TEXT");
|
|
1706
1761
|
}
|
|
@@ -1731,6 +1786,8 @@ var init_store = __esm(() => {
|
|
|
1731
1786
|
init_store();
|
|
1732
1787
|
PersistenceError = class PersistenceError extends Schema15.TaggedErrorClass()("RelayStore.PersistenceError", { operation: Schema15.String, message: Schema15.String }) {
|
|
1733
1788
|
};
|
|
1789
|
+
TaskProgressError = class TaskProgressError extends Schema15.TaggedErrorClass()("RelayStore.TaskProgressError", { status: Schema15.Literals([403, 409]), message: Schema15.String }) {
|
|
1790
|
+
};
|
|
1734
1791
|
Service2 = class Service2 extends Context2.Service()("@cohall/RelayStore") {
|
|
1735
1792
|
};
|
|
1736
1793
|
});
|
|
@@ -2184,7 +2241,7 @@ Content-Length: 0\r
|
|
|
2184
2241
|
};
|
|
2185
2242
|
const api = async (request, url) => {
|
|
2186
2243
|
if (url.pathname === "/api/health" && request.method === "GET") {
|
|
2187
|
-
return json({ ok: true, version, taskAttachments: true });
|
|
2244
|
+
return json({ ok: true, version, taskAttachments: true, taskProgress: true });
|
|
2188
2245
|
}
|
|
2189
2246
|
if (url.pathname === "/api/auth/pair" && request.method === "POST") {
|
|
2190
2247
|
return run2(Effect15.gen(function* () {
|
|
@@ -2194,7 +2251,8 @@ Content-Length: 0\r
|
|
|
2194
2251
|
})).then((value) => json(value, 201)).catch(() => json({ error: "Pairing credential is invalid, expired, or already used" }, 401));
|
|
2195
2252
|
}
|
|
2196
2253
|
const attachmentRoute = request.method === "GET" && /^\/api\/tasks\/[^/]+\/attachments(?:\/[^/]+)?$/.test(url.pathname);
|
|
2197
|
-
const
|
|
2254
|
+
const progressRoute = request.method === "POST" && /^\/api\/tasks\/[^/]+\/progress$/.test(url.pathname);
|
|
2255
|
+
const principal = await principalFor(request, attachmentRoute || progressRoute);
|
|
2198
2256
|
if (principal === undefined) {
|
|
2199
2257
|
return json({ error: "Unauthorized" }, 401);
|
|
2200
2258
|
}
|
|
@@ -2313,6 +2371,20 @@ Content-Length: 0\r
|
|
|
2313
2371
|
if (request.method === "GET" && trace?.[1] !== undefined) {
|
|
2314
2372
|
return json(yield* store.traceTask(yield* pathId(TaskId, trace[1])));
|
|
2315
2373
|
}
|
|
2374
|
+
const progress = url.pathname.match(/^\/api\/tasks\/([^/]+)\/progress$/);
|
|
2375
|
+
if (request.method === "POST" && progress?.[1] !== undefined) {
|
|
2376
|
+
const id = yield* pathId(TaskId, progress[1]);
|
|
2377
|
+
const input = yield* body(request, decodeTaskProgressInput);
|
|
2378
|
+
const task2 = yield* store.getTask(id);
|
|
2379
|
+
const deviceId = principal === "owner" ? task2.targetDeviceId : principal.deviceId;
|
|
2380
|
+
if (deviceId === undefined) {
|
|
2381
|
+
return yield* new RequestError({
|
|
2382
|
+
status: 403,
|
|
2383
|
+
message: "Only the target device can report progress"
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
return json(yield* store.reportTaskProgress(id, deviceId, input.note));
|
|
2387
|
+
}
|
|
2316
2388
|
const cancel2 = url.pathname.match(/^\/api\/tasks\/([^/]+)\/cancel$/);
|
|
2317
2389
|
if (request.method === "POST" && cancel2?.[1] !== undefined) {
|
|
2318
2390
|
const id = yield* pathId(TaskId, cancel2[1]);
|
|
@@ -2327,7 +2399,7 @@ Content-Length: 0\r
|
|
|
2327
2399
|
return json(yield* store.threadContext(yield* pathId(ThreadId, thread[1])));
|
|
2328
2400
|
}
|
|
2329
2401
|
return yield* new RequestError({ status: 404, message: "Route not found" });
|
|
2330
|
-
}).pipe(Effect15.mapError((cause) => cause instanceof RequestError ? cause : new RequestError({
|
|
2402
|
+
}).pipe(Effect15.mapError((cause) => cause instanceof RequestError ? cause : cause._tag === "RelayStore.TaskProgressError" ? new RequestError({ status: cause.status, message: cause.message }) : new RequestError({
|
|
2331
2403
|
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,
|
|
2332
2404
|
message: cause.message
|
|
2333
2405
|
})));
|
|
@@ -2598,6 +2670,16 @@ var make = (options) => {
|
|
|
2598
2670
|
});
|
|
2599
2671
|
}),
|
|
2600
2672
|
getTask: (taskId) => request("RelayClient.getTask", `/api/tasks/${encodeURIComponent(taskId)}`, Task),
|
|
2673
|
+
reportTaskProgress: (taskId, input) => Effect2.gen(function* () {
|
|
2674
|
+
const health = yield* request("RelayClient.progressSupport", "/api/health", Schema2.Struct({ taskProgress: Schema2.optionalKey(Schema2.Boolean) }));
|
|
2675
|
+
if (health.taskProgress !== true) {
|
|
2676
|
+
return yield* new RelayRequestError({
|
|
2677
|
+
operation: "RelayClient.reportTaskProgress",
|
|
2678
|
+
message: "Upgrade the Cohall relay before reporting task progress"
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
return yield* request("RelayClient.reportTaskProgress", `/api/tasks/${encodeURIComponent(taskId)}/progress`, TaskProgress, { method: "POST", body: JSON.stringify(input) });
|
|
2682
|
+
}),
|
|
2601
2683
|
listAttachments: (taskId) => request("RelayClient.listAttachments", `/api/tasks/${encodeURIComponent(taskId)}/attachments`, Schema2.Array(TaskAttachment)),
|
|
2602
2684
|
readAttachment: (taskId, name, direction) => Effect2.tryPromise({
|
|
2603
2685
|
try: async (signal) => {
|
|
@@ -3665,7 +3747,8 @@ var TaskResult = Schema5.Struct({
|
|
|
3665
3747
|
bot_id: Schema5.optionalKey(BotId),
|
|
3666
3748
|
result: Schema5.optionalKey(Schema5.String),
|
|
3667
3749
|
error: Schema5.optionalKey(Schema5.String),
|
|
3668
|
-
inbox_warning: Schema5.optionalKey(Schema5.String)
|
|
3750
|
+
inbox_warning: Schema5.optionalKey(Schema5.String),
|
|
3751
|
+
progress: Schema5.optionalKey(TaskProgress)
|
|
3669
3752
|
});
|
|
3670
3753
|
var matchingDevices = (devices, target) => {
|
|
3671
3754
|
const value = target.replace(/^@/, "");
|
|
@@ -3717,7 +3800,8 @@ var taskResult = (task) => TaskResult.make({
|
|
|
3717
3800
|
target_device_id: task.targetDeviceId,
|
|
3718
3801
|
...task.botId === undefined ? {} : { bot_id: task.botId },
|
|
3719
3802
|
...task.result === undefined ? {} : { result: task.result },
|
|
3720
|
-
...task.error === undefined ? {} : { error: task.error }
|
|
3803
|
+
...task.error === undefined ? {} : { error: task.error },
|
|
3804
|
+
...task.progress === undefined ? {} : { progress: task.progress }
|
|
3721
3805
|
});
|
|
3722
3806
|
var acknowledgedTaskResult = async (client, task) => {
|
|
3723
3807
|
const result = taskResult(task);
|
|
@@ -3803,7 +3887,7 @@ var followTaskTrace = Effect5.fn("Cohall.followTaskTrace")(function* (client, ta
|
|
|
3803
3887
|
let revision;
|
|
3804
3888
|
const poll = client.traceTask(taskId).pipe(Effect5.tap((trace) => {
|
|
3805
3889
|
const latest = trace.events.at(-1);
|
|
3806
|
-
const nextRevision = `${trace.status}:${latest?.kind ?? "none"}:${latest?.at ?? "none"}`;
|
|
3890
|
+
const nextRevision = `${trace.status}:${latest?.kind ?? "none"}:${latest?.at ?? "none"}:${trace.progress?.at ?? ""}:${trace.progress?.note ?? ""}`;
|
|
3807
3891
|
if (nextRevision === revision) {
|
|
3808
3892
|
return Effect5.succeed(undefined);
|
|
3809
3893
|
}
|
|
@@ -3850,6 +3934,7 @@ var allDeviceHealth = (devices, localVersion) => {
|
|
|
3850
3934
|
providers: device.providers,
|
|
3851
3935
|
workspaces: device.workspaces.map((workspace) => workspace.path),
|
|
3852
3936
|
last_seen_at: device.lastSeenAt,
|
|
3937
|
+
...device.queue === undefined ? {} : { queue: device.queue },
|
|
3853
3938
|
warnings
|
|
3854
3939
|
};
|
|
3855
3940
|
});
|
|
@@ -5530,6 +5615,39 @@ var collectOutputs = async (directory) => {
|
|
|
5530
5615
|
return files;
|
|
5531
5616
|
};
|
|
5532
5617
|
|
|
5618
|
+
// apps/device/src/mcp-diagnostics.ts
|
|
5619
|
+
init_src();
|
|
5620
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5621
|
+
import {
|
|
5622
|
+
getDefaultEnvironment,
|
|
5623
|
+
StdioClientTransport
|
|
5624
|
+
} from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5625
|
+
var checkMcp = async (entrypoint, relayUrl, token) => {
|
|
5626
|
+
const client = new Client({ name: "cohall-doctor", version });
|
|
5627
|
+
const transport = new StdioClientTransport({
|
|
5628
|
+
command: process.execPath,
|
|
5629
|
+
args: [entrypoint, "mcp"],
|
|
5630
|
+
env: {
|
|
5631
|
+
...getDefaultEnvironment(),
|
|
5632
|
+
COHALL_CONFIG: configurationPath(),
|
|
5633
|
+
COHALL_RELAY_URL: relayUrl,
|
|
5634
|
+
COHALL_CLIENT_TOKEN: token
|
|
5635
|
+
},
|
|
5636
|
+
stderr: "pipe"
|
|
5637
|
+
});
|
|
5638
|
+
try {
|
|
5639
|
+
await client.connect(transport, { timeout: 5000 });
|
|
5640
|
+
const { tools } = await client.listTools(undefined, { timeout: 5000 });
|
|
5641
|
+
if (tools.length === 0)
|
|
5642
|
+
return { status: "error", error: "MCP server exposed no tools" };
|
|
5643
|
+
return { status: "ok", tool_count: tools.length };
|
|
5644
|
+
} catch (cause) {
|
|
5645
|
+
return { status: "error", error: cause instanceof Error ? cause.message : String(cause) };
|
|
5646
|
+
} finally {
|
|
5647
|
+
await client.close();
|
|
5648
|
+
}
|
|
5649
|
+
};
|
|
5650
|
+
|
|
5533
5651
|
// apps/device/src/cli.ts
|
|
5534
5652
|
var valueOptions = new Set([
|
|
5535
5653
|
"attach",
|
|
@@ -5585,6 +5703,7 @@ Usage:
|
|
|
5585
5703
|
[--grok-gateway path]
|
|
5586
5704
|
cohall config
|
|
5587
5705
|
cohall devices
|
|
5706
|
+
cohall progress [task-id] --message <note>
|
|
5588
5707
|
cohall bots
|
|
5589
5708
|
cohall send [@device-or-bot] [prompt] [delegate options]
|
|
5590
5709
|
cohall delegate [prompt] [--target @device-or-bot] [--provider provider]
|
|
@@ -6132,16 +6251,29 @@ var runCli = async (command, raw) => {
|
|
|
6132
6251
|
return;
|
|
6133
6252
|
});
|
|
6134
6253
|
const clientToken = process.env.COHALL_CLIENT_TOKEN ?? storedCredentials.clientToken;
|
|
6254
|
+
const mcp = clientToken === undefined ? { status: "not configured" } : await checkMcp(process.argv[1] ?? "", relayUrl, clientToken);
|
|
6135
6255
|
const hasDeviceCredential = process.env.COHALL_DEVICE_TOKEN !== undefined || storedCredentials.deviceToken !== undefined;
|
|
6136
6256
|
const deviceId = process.env.COHALL_DEVICE_ID ?? configuration2?.deviceId;
|
|
6137
|
-
const
|
|
6138
|
-
|
|
6139
|
-
|
|
6257
|
+
const clientAuthentication = clientToken === undefined ? { status: "not configured" } : response?.ok !== true ? { status: "not checked" } : await Effect10.runPromise(exports_src.make({ baseUrl: relayUrl, token: clientToken }).devices().pipe(Effect10.match({
|
|
6258
|
+
onSuccess: (devices) => ({ status: "ok", devices }),
|
|
6259
|
+
onFailure: (error) => {
|
|
6260
|
+
const httpStatus = error instanceof RelayRequestError ? error.status : undefined;
|
|
6261
|
+
const reason = httpStatus === 401 || httpStatus === 403 ? "Client credential was rejected by the relay" : "Client relay check failed";
|
|
6262
|
+
return {
|
|
6263
|
+
status: "error",
|
|
6264
|
+
...httpStatus === undefined ? {} : { http_status: httpStatus },
|
|
6265
|
+
error: `${reason}: ${error.message}`
|
|
6266
|
+
};
|
|
6267
|
+
}
|
|
6268
|
+
})));
|
|
6269
|
+
const currentDevice = clientAuthentication.status === "ok" ? clientAuthentication.devices.find((device) => device.id === deviceId) : undefined;
|
|
6140
6270
|
const versionWarning = deviceVersionWarning(version, currentDevice?.version);
|
|
6141
6271
|
const warnings = [
|
|
6142
6272
|
...response?.ok === true ? [] : ["Relay is unreachable"],
|
|
6143
6273
|
...versionWarning === undefined ? [] : [versionWarning],
|
|
6144
|
-
...
|
|
6274
|
+
...mcp.status === "error" ? [`MCP check failed: ${mcp.error}`] : [],
|
|
6275
|
+
...clientAuthentication.status === "error" ? [clientAuthentication.error] : [],
|
|
6276
|
+
...hasDeviceCredential && response?.ok === true && clientAuthentication.status !== "error" && currentDevice?.status !== "online" && currentDevice?.status !== "busy" ? [
|
|
6145
6277
|
clientToken === undefined ? "Device status cannot be checked without a client credential" : "Device daemon is not connected or is not registered with the relay"
|
|
6146
6278
|
] : [],
|
|
6147
6279
|
...selectedProviders?.flatMap((provider) => provider === "grok-bot" ? grokBots === undefined ? [
|
|
@@ -6154,6 +6286,8 @@ var runCli = async (command, raw) => {
|
|
|
6154
6286
|
relay_url: relayUrl,
|
|
6155
6287
|
config_path: configurationPath(),
|
|
6156
6288
|
client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || storedCredentials.clientToken !== undefined,
|
|
6289
|
+
client_authentication: clientAuthentication.status === "ok" ? { status: "ok" } : clientAuthentication,
|
|
6290
|
+
mcp,
|
|
6157
6291
|
device_credential: hasDeviceCredential,
|
|
6158
6292
|
workspaces: configuration2?.workspaces ?? [],
|
|
6159
6293
|
providers: providerExecutables,
|
|
@@ -6220,6 +6354,18 @@ var runCli = async (command, raw) => {
|
|
|
6220
6354
|
return;
|
|
6221
6355
|
}
|
|
6222
6356
|
const { configuration, relay } = await client();
|
|
6357
|
+
if (command === "progress") {
|
|
6358
|
+
allowOptions(arguments_, ["message"]);
|
|
6359
|
+
const rawId = arguments_.positionals.length === 0 ? configuration.mcpTaskId : identifier(arguments_, "task id");
|
|
6360
|
+
if (rawId === undefined)
|
|
6361
|
+
throw new Error("Task id is required outside delegated work");
|
|
6362
|
+
const note = option(arguments_, "message");
|
|
6363
|
+
if (note === undefined)
|
|
6364
|
+
throw new Error("--message is required");
|
|
6365
|
+
const input = Schema11.decodeUnknownSync(TaskProgressInput)({ note });
|
|
6366
|
+
print(await Effect10.runPromise(relay.reportTaskProgress(Schema11.decodeUnknownSync(TaskId)(rawId), input)));
|
|
6367
|
+
return;
|
|
6368
|
+
}
|
|
6223
6369
|
if (command === "devices") {
|
|
6224
6370
|
allowOptions(arguments_, []);
|
|
6225
6371
|
noPositionals(arguments_, command);
|
|
@@ -6491,6 +6637,7 @@ ${task.context}`;
|
|
|
6491
6637
|
"Complete the delegated task using this device's local workspace, tools, credentials, and signed-in services.",
|
|
6492
6638
|
"Never read, reveal, copy, or use Cohall configuration files or Cohall authentication tokens.",
|
|
6493
6639
|
"Return a concise, complete result with the evidence the sending agent needs.",
|
|
6640
|
+
"For long tasks, optionally report a brief milestone with `cohall progress --message 'Running tests'`. Do not include logs or secrets in progress notes.",
|
|
6494
6641
|
...files === undefined ? [] : [
|
|
6495
6642
|
...files.inputNames.length === 0 ? [] : [
|
|
6496
6643
|
`Input directory: ${files.input}`,
|
|
@@ -6905,15 +7052,81 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6905
7052
|
import { Effect as Effect12, Schema as Schema13 } from "effect";
|
|
6906
7053
|
import * as z from "zod/v4";
|
|
6907
7054
|
import { writeFile as writeFile8 } from "node:fs/promises";
|
|
6908
|
-
|
|
6909
|
-
|
|
7055
|
+
|
|
7056
|
+
// apps/device/src/mcp-build-notice.ts
|
|
7057
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
7058
|
+
import { stat as stat6 } from "node:fs/promises";
|
|
7059
|
+
import { promisify } from "node:util";
|
|
7060
|
+
var execute2 = promisify(execFile3);
|
|
7061
|
+
var fingerprint = async (entrypoint) => stat6(entrypoint, { bigint: true }).then((file) => `${file.dev}:${file.ino}:${file.size}:${file.mtimeNs}:${file.ctimeNs}`).catch(() => {
|
|
7062
|
+
return;
|
|
6910
7063
|
});
|
|
7064
|
+
var createMcpBuildNotice = async (entrypoint, runningVersion, {
|
|
7065
|
+
checkIntervalMs = 60000,
|
|
7066
|
+
probeTimeoutMs = 2000,
|
|
7067
|
+
now: now2 = Date.now
|
|
7068
|
+
} = {}) => {
|
|
7069
|
+
let previous = entrypoint === undefined ? undefined : await fingerprint(entrypoint);
|
|
7070
|
+
let nextCheckAt = 0;
|
|
7071
|
+
const reported = new Set;
|
|
7072
|
+
return async (result) => {
|
|
7073
|
+
if (entrypoint === undefined || now2() < nextCheckAt)
|
|
7074
|
+
return result;
|
|
7075
|
+
nextCheckAt = now2() + checkIntervalMs;
|
|
7076
|
+
const current = await fingerprint(entrypoint);
|
|
7077
|
+
if (current === undefined || current === previous)
|
|
7078
|
+
return result;
|
|
7079
|
+
const installedVersion2 = await execute2(process.execPath, [entrypoint, "--version"], {
|
|
7080
|
+
timeout: probeTimeoutMs,
|
|
7081
|
+
killSignal: "SIGKILL",
|
|
7082
|
+
maxBuffer: 4096,
|
|
7083
|
+
windowsHide: true
|
|
7084
|
+
}).then(({ stdout }) => stdout.trim()).catch(() => {
|
|
7085
|
+
return;
|
|
7086
|
+
});
|
|
7087
|
+
if (installedVersion2 === undefined || !/^\d+\.\d+\.\d+(?:-[\da-zA-Z.-]+)?(?:\+[\da-zA-Z.-]+)?$/.test(installedVersion2)) {
|
|
7088
|
+
return result;
|
|
7089
|
+
}
|
|
7090
|
+
previous = current;
|
|
7091
|
+
if (installedVersion2 === runningVersion || reported.has(installedVersion2))
|
|
7092
|
+
return result;
|
|
7093
|
+
reported.add(installedVersion2);
|
|
7094
|
+
return {
|
|
7095
|
+
...result,
|
|
7096
|
+
content: [
|
|
7097
|
+
...result.content,
|
|
7098
|
+
{
|
|
7099
|
+
type: "text",
|
|
7100
|
+
text: `Cohall MCP is still running ${runningVersion}; its executable is now ${installedVersion2}. Restart the Cohall MCP connection in your agent host to load it.`
|
|
7101
|
+
}
|
|
7102
|
+
]
|
|
7103
|
+
};
|
|
7104
|
+
};
|
|
7105
|
+
};
|
|
7106
|
+
|
|
7107
|
+
// apps/device/src/mcp.ts
|
|
6911
7108
|
var runMcp = async (configuration) => {
|
|
7109
|
+
const buildNotice = await createMcpBuildNotice(process.argv[1], version);
|
|
7110
|
+
const output = (value) => buildNotice({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }] });
|
|
6912
7111
|
const client2 = exports_src.make({
|
|
6913
7112
|
baseUrl: configuration.relayUrl,
|
|
6914
7113
|
token: configuration.token
|
|
6915
7114
|
});
|
|
6916
7115
|
const server = new McpServer({ name: "cohall", version });
|
|
7116
|
+
server.registerTool("task_progress", {
|
|
7117
|
+
title: "Report task progress",
|
|
7118
|
+
description: `Replace the latest progress note for a running task on this device. Use brief milestones, up to ${maxProgressNoteBytes} UTF-8 bytes. Inherits the delegated task ID when omitted.`,
|
|
7119
|
+
inputSchema: {
|
|
7120
|
+
task_id: z.string().uuid().optional(),
|
|
7121
|
+
note: z.string().min(1).max(maxProgressNoteBytes)
|
|
7122
|
+
}
|
|
7123
|
+
}, async ({ task_id, note }) => {
|
|
7124
|
+
const id = task_id ?? configuration.mcpTaskId;
|
|
7125
|
+
if (id === undefined)
|
|
7126
|
+
throw new Error("Task id is required outside delegated work");
|
|
7127
|
+
const input = Schema13.decodeUnknownSync(TaskProgressInput)({ note });
|
|
7128
|
+
return output(await Effect12.runPromise(client2.reportTaskProgress(Schema13.decodeUnknownSync(TaskId)(id), input)));
|
|
7129
|
+
});
|
|
6917
7130
|
server.registerTool("list_devices", {
|
|
6918
7131
|
title: "List Cohall devices",
|
|
6919
7132
|
description: "List devices, their availability, local providers, capabilities, and allowed workspaces.",
|
|
@@ -7003,7 +7216,7 @@ var runMcp = async (configuration) => {
|
|
|
7003
7216
|
});
|
|
7004
7217
|
server.registerTool("task_trace", {
|
|
7005
7218
|
title: "Trace a delegated task",
|
|
7006
|
-
description: "Read the
|
|
7219
|
+
description: "Read the relay and device lifecycle and latest worker progress note for troubleshooting a Cohall task.",
|
|
7007
7220
|
inputSchema: { task_id: z.string().uuid() }
|
|
7008
7221
|
}, async ({ task_id }) => output(await Effect12.runPromise(client2.traceTask(TaskId.make(task_id)))));
|
|
7009
7222
|
server.registerTool("wait_task", {
|
|
@@ -7071,4 +7284,4 @@ await main().catch((cause) => {
|
|
|
7071
7284
|
process.exitCode = 1;
|
|
7072
7285
|
});
|
|
7073
7286
|
|
|
7074
|
-
//# debugId=
|
|
7287
|
+
//# debugId=48BD90839B9C2B1564756E2164756E21
|