@akshar5/cohall 0.10.1 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/bin/cohall.js +46 -29
- package/bin/cohall.js.map +8 -8
- package/docs/services.md +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.2](https://github.com/AksharP5/cohall/compare/v0.10.1...v0.10.2) (2026-10-04)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **client:** cancel pairing response body reads ([#165](https://github.com/AksharP5/cohall/issues/165)) ([92702ee](https://github.com/AksharP5/cohall/commit/92702eed5ce582f7086fa12f45bd5acb1cc80a24))
|
|
9
|
+
* **device:** allow workspace names beginning with two dots ([#163](https://github.com/AksharP5/cohall/issues/163)) ([5764e37](https://github.com/AksharP5/cohall/commit/5764e37935502e861316c46b36b0e0db868a66c4))
|
|
10
|
+
* **device:** reject attachment FIFOs without blocking ([#162](https://github.com/AksharP5/cohall/issues/162)) ([7eae2ca](https://github.com/AksharP5/cohall/commit/7eae2ca46d1dfbbb6fa91a0236d137b52dc0f9c3))
|
|
11
|
+
* **mcp:** stop cancelled attachment downloads before writing ([#164](https://github.com/AksharP5/cohall/issues/164)) ([1b91dc4](https://github.com/AksharP5/cohall/commit/1b91dc453e44f72051729fe32787d33b1d275b4e))
|
|
12
|
+
* **relay:** change addresses while workers are connected ([#166](https://github.com/AksharP5/cohall/issues/166)) ([87c3baa](https://github.com/AksharP5/cohall/commit/87c3baa886bd887bb7aaf00d76bcea44e8cbe000))
|
|
13
|
+
|
|
3
14
|
## [0.10.1](https://github.com/AksharP5/cohall/compare/v0.10.0...v0.10.1) (2026-10-04)
|
|
4
15
|
|
|
5
16
|
|
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.10.
|
|
20
|
+
var version = "0.10.2", 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, TaskRequestId, TaskRunId, ClarificationId, AuthSessionId, OperationId, Timestamp, TaskDeadline, taskDeadlineError = "Task deadline exceeded", taskDeadlinePassed = (task, time = Date.now()) => task.expiresAt !== undefined && Date.parse(task.expiresAt) <= time, Platform, Provider, DeviceStatus, TaskStatus, OperationStatus, TaskTraceEventKind, MessageRole, ConnectionRole, AuthSession, CreatePairingInput, PairingCredential, ExchangePairingInput, SessionCredential, PairingResult, Capability, Workspace, Device, maxDevicePageDevices = 16, maxDevicePageResponseBytes, DevicePage, minimumDeviceOperationVersion = "0.5.0", parsedVersion = (value) => {
|
|
23
23
|
const match = /^v?(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
|
|
@@ -2713,6 +2713,14 @@ Content-Length: 0\r
|
|
|
2713
2713
|
return yield* store.exchangePairing(input.token);
|
|
2714
2714
|
})).then((value) => json(value, 201)).catch(() => json({ error: "Pairing credential is invalid, expired, or already used" }, 401));
|
|
2715
2715
|
}
|
|
2716
|
+
if (url.pathname === "/api/device/credential" && request.method === "GET") {
|
|
2717
|
+
const authorization = request.headers.get("authorization");
|
|
2718
|
+
const token = authorization?.startsWith("Bearer ") === true ? authorization.slice("Bearer ".length) : undefined;
|
|
2719
|
+
if (token === undefined || await authenticate(token, "device") === undefined) {
|
|
2720
|
+
return json({ error: "Unauthorized" }, 401);
|
|
2721
|
+
}
|
|
2722
|
+
return json({ ok: true });
|
|
2723
|
+
}
|
|
2716
2724
|
const attachmentRoute = request.method === "GET" && /^\/api\/tasks\/[^/]+\/attachments(?:\/[^/]+)?$/.test(url.pathname);
|
|
2717
2725
|
const progressRoute = request.method === "POST" && /^\/api\/tasks\/[^/]+\/progress$/.test(url.pathname);
|
|
2718
2726
|
const inputRoute = request.method === "POST" && /^\/api\/tasks\/[^/]+\/input$/.test(url.pathname);
|
|
@@ -3174,6 +3182,7 @@ var make = (options) => {
|
|
|
3174
3182
|
});
|
|
3175
3183
|
const devicePage = (after) => request("RelayClient.devices", `/api/devices/page${after === undefined ? "" : `?after=${encodeURIComponent(after)}`}`, DevicePage, undefined, maxDevicePageResponseBytes);
|
|
3176
3184
|
return Service.of({
|
|
3185
|
+
verifyDeviceCredential: () => request("RelayClient.verifyDeviceCredential", "/api/device/credential", Schema2.Struct({ ok: Schema2.Literal(true) })).pipe(Effect2.asVoid),
|
|
3177
3186
|
devices: () => Effect2.gen(function* () {
|
|
3178
3187
|
const first = yield* devicePage().pipe(Effect2.catch((cause) => cause instanceof RelayRequestError && cause.status === 404 ? request("RelayClient.devices", "/api/devices", Schema2.Array(Device)) : Effect2.fail(cause)));
|
|
3179
3188
|
if (!("devices" in first))
|
|
@@ -3317,29 +3326,30 @@ var make = (options) => {
|
|
|
3317
3326
|
};
|
|
3318
3327
|
var exchangePairing = (baseUrl, input) => Effect2.gen(function* () {
|
|
3319
3328
|
const operation = "RelayClient.exchangePairing";
|
|
3320
|
-
const
|
|
3321
|
-
try: (signal) =>
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3329
|
+
const json = yield* Effect2.tryPromise({
|
|
3330
|
+
try: async (signal) => {
|
|
3331
|
+
const response = await fetch(`${normalizeBaseUrl(baseUrl)}/api/auth/pair`, {
|
|
3332
|
+
method: "POST",
|
|
3333
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(30000)]),
|
|
3334
|
+
headers: { "content-type": "application/json" },
|
|
3335
|
+
body: JSON.stringify(input)
|
|
3336
|
+
});
|
|
3337
|
+
if (!response.ok) {
|
|
3338
|
+
throw new RelayRequestError({
|
|
3339
|
+
operation,
|
|
3340
|
+
message: await responseMessage(response, operation),
|
|
3341
|
+
status: response.status
|
|
3342
|
+
});
|
|
3343
|
+
}
|
|
3344
|
+
return jsonBody(response, operation).catch((cause) => {
|
|
3345
|
+
throw cause instanceof RelayDecodeError ? cause : new RelayDecodeError({ operation, message: String(cause) });
|
|
3346
|
+
});
|
|
3347
|
+
},
|
|
3348
|
+
catch: (cause) => cause instanceof RelayRequestError || cause instanceof RelayDecodeError ? cause : new RelayRequestError({
|
|
3328
3349
|
operation,
|
|
3329
3350
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
3330
3351
|
})
|
|
3331
3352
|
});
|
|
3332
|
-
if (!response.ok) {
|
|
3333
|
-
return yield* Effect2.fail(new RelayRequestError({
|
|
3334
|
-
operation,
|
|
3335
|
-
message: yield* Effect2.promise(() => responseMessage(response, operation)),
|
|
3336
|
-
status: response.status
|
|
3337
|
-
}));
|
|
3338
|
-
}
|
|
3339
|
-
const json = yield* Effect2.tryPromise({
|
|
3340
|
-
try: () => jsonBody(response, operation),
|
|
3341
|
-
catch: (cause) => cause instanceof RelayDecodeError ? cause : new RelayDecodeError({ operation, message: String(cause) })
|
|
3342
|
-
});
|
|
3343
3353
|
return yield* Schema2.decodeUnknownEffect(PairingResult)(json).pipe(Effect2.mapError((cause) => new RelayDecodeError({ operation, message: String(cause) })));
|
|
3344
3354
|
});
|
|
3345
3355
|
var layer = (options) => Layer.succeed(Service, make(options));
|
|
@@ -6489,7 +6499,7 @@ var isLoopback = (url) => url.hostname === "localhost" || url.hostname === "[::1
|
|
|
6489
6499
|
var verifyClientCredential = async (relayUrl, token) => {
|
|
6490
6500
|
await Effect8.runPromise(exports_src.make({ baseUrl: relayUrl, token }).devices());
|
|
6491
6501
|
};
|
|
6492
|
-
var
|
|
6502
|
+
var verifyLegacyDeviceCredential = (relayUrl, token) => new Promise((resolvePromise, reject) => {
|
|
6493
6503
|
const socket = new WebSocket(websocketUrl(relayUrl), {
|
|
6494
6504
|
handshakeTimeout: 1e4,
|
|
6495
6505
|
maxPayload: 1024 * 1024,
|
|
@@ -6528,6 +6538,12 @@ var verifyDeviceCredential = (relayUrl, token) => new Promise((resolvePromise, r
|
|
|
6528
6538
|
socket.once("error", (cause) => finish(cause));
|
|
6529
6539
|
socket.once("close", () => finish(new Error("New relay rejected the stored device credential")));
|
|
6530
6540
|
});
|
|
6541
|
+
var verifyDeviceCredential = (relayUrl, token) => Effect8.runPromise(exports_src.make({ baseUrl: relayUrl, token }).verifyDeviceCredential()).catch((cause) => {
|
|
6542
|
+
if (cause instanceof RelayRequestError && (cause.status === 401 || cause.status === 404)) {
|
|
6543
|
+
return verifyLegacyDeviceCredential(relayUrl, token);
|
|
6544
|
+
}
|
|
6545
|
+
throw cause;
|
|
6546
|
+
});
|
|
6531
6547
|
var switchRelay = async (options) => {
|
|
6532
6548
|
if (process.env.COHALL_RELAY_URL !== undefined) {
|
|
6533
6549
|
throw new Error("Unset COHALL_RELAY_URL before changing the stored relay URL");
|
|
@@ -6623,7 +6639,7 @@ var readInputAttachments = async (paths2) => {
|
|
|
6623
6639
|
if (!source.isFile()) {
|
|
6624
6640
|
throw new Error(`Attachment ${name} must be a regular file`);
|
|
6625
6641
|
}
|
|
6626
|
-
const handle = await open3(path, constants3.O_RDONLY | (process.platform === "win32" ? 0 : constants3.O_NOFOLLOW));
|
|
6642
|
+
const handle = await open3(path, constants3.O_RDONLY | (process.platform === "win32" ? 0 : constants3.O_NOFOLLOW | constants3.O_NONBLOCK));
|
|
6627
6643
|
try {
|
|
6628
6644
|
const metadata = await handle.stat();
|
|
6629
6645
|
if (!metadata.isFile() || metadata.dev !== source.dev || metadata.ino !== source.ino || metadata.size < 1 || metadata.size > maxAttachmentBytes) {
|
|
@@ -6680,7 +6696,7 @@ var collectOutputs = async (directory) => {
|
|
|
6680
6696
|
throw new Error(`Output ${name} repeats another file name`);
|
|
6681
6697
|
}
|
|
6682
6698
|
names.add(key);
|
|
6683
|
-
const handle = await open3(join7(directory, name), constants3.O_RDONLY | (process.platform === "win32" ? 0 : constants3.O_NOFOLLOW));
|
|
6699
|
+
const handle = await open3(join7(directory, name), constants3.O_RDONLY | (process.platform === "win32" ? 0 : constants3.O_NOFOLLOW | constants3.O_NONBLOCK));
|
|
6684
6700
|
try {
|
|
6685
6701
|
const metadata = await handle.stat();
|
|
6686
6702
|
const pathMetadata = await lstat2(join7(directory, name));
|
|
@@ -7643,7 +7659,7 @@ import { Effect as Effect11, Schedule as Schedule3, Schema as Schema12 } from "e
|
|
|
7643
7659
|
import { constants as constants4 } from "node:fs";
|
|
7644
7660
|
import { open as open5, realpath as realpath4, stat as stat5 } from "node:fs/promises";
|
|
7645
7661
|
import { arch, hostname as hostname3, platform as platform4 } from "node:os";
|
|
7646
|
-
import { basename as basename3, isAbsolute as isAbsolute5, relative as relative2 } from "node:path";
|
|
7662
|
+
import { basename as basename3, isAbsolute as isAbsolute5, relative as relative2, sep } from "node:path";
|
|
7647
7663
|
import { WebSocket as WebSocket2 } from "ws";
|
|
7648
7664
|
var maxQueuedRelayMessages = 8;
|
|
7649
7665
|
|
|
@@ -7724,7 +7740,7 @@ var allowedWorkspace = async (configuration, requested) => {
|
|
|
7724
7740
|
const candidate = await realpath4(selected);
|
|
7725
7741
|
const allowed = configuration.workspaces.some((root) => {
|
|
7726
7742
|
const child = relative2(root, candidate);
|
|
7727
|
-
return child
|
|
7743
|
+
return child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute5(child);
|
|
7728
7744
|
});
|
|
7729
7745
|
if (!allowed) {
|
|
7730
7746
|
throw new Error(`Workspace ${candidate} is outside this device's configured workspace roots`);
|
|
@@ -8446,11 +8462,12 @@ var runMcp = async (configuration) => {
|
|
|
8446
8462
|
output_path: z.string(),
|
|
8447
8463
|
direction: z.enum(["input", "output"]).optional()
|
|
8448
8464
|
}
|
|
8449
|
-
}, async ({ task_id, name, output_path, direction }) => {
|
|
8465
|
+
}, async ({ task_id, name, output_path, direction }, { signal }) => {
|
|
8450
8466
|
const safeName = Schema13.decodeUnknownSync(AttachmentName)(name);
|
|
8451
8467
|
const safeDirection = direction === undefined ? undefined : Schema13.decodeUnknownSync(AttachmentDirection)(direction);
|
|
8452
|
-
const data = await Effect12.runPromise(client2.readAttachment(TaskId.make(task_id), safeName, safeDirection));
|
|
8453
|
-
|
|
8468
|
+
const data = await Effect12.runPromise(client2.readAttachment(TaskId.make(task_id), safeName, safeDirection), { signal });
|
|
8469
|
+
signal.throwIfAborted();
|
|
8470
|
+
await writeFile8(output_path, data, { flag: "wx", mode: 384, signal });
|
|
8454
8471
|
return output({ task_id, name: safeName, output_path, bytes: data.length });
|
|
8455
8472
|
});
|
|
8456
8473
|
server.registerTool("task_trace", {
|
|
@@ -8536,4 +8553,4 @@ await main().catch((cause) => {
|
|
|
8536
8553
|
process.exitCode = 1;
|
|
8537
8554
|
});
|
|
8538
8555
|
|
|
8539
|
-
//# debugId=
|
|
8556
|
+
//# debugId=49CCA945DC24F88C64756E2164756E21
|