@akshar5/cohall 0.10.0 → 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 +21 -0
- package/README.md +9 -4
- package/bin/cohall.js +151 -83
- package/bin/cohall.js.map +15 -14
- package/docs/install.md +4 -0
- package/docs/integrations.md +4 -0
- package/docs/services.md +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
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
|
+
|
|
14
|
+
## [0.10.1](https://github.com/AksharP5/cohall/compare/v0.10.0...v0.10.1) (2026-10-04)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **cli:** prevent oversized files from bypassing input limits ([#160](https://github.com/AksharP5/cohall/issues/160)) ([c6ef1d1](https://github.com/AksharP5/cohall/commit/c6ef1d1b31454b3baf755944cd0c6ef7021594c3))
|
|
20
|
+
* **config:** ignore invalid XDG directories ([#157](https://github.com/AksharP5/cohall/issues/157)) ([10e9acb](https://github.com/AksharP5/cohall/commit/10e9acbfdd2839e12b384467b568260c183a88ef))
|
|
21
|
+
* **providers:** preserve multipart OpenCode answers ([#161](https://github.com/AksharP5/cohall/issues/161)) ([219c988](https://github.com/AksharP5/cohall/commit/219c988ccfd5b3725242da5cbcab1ac27da63cf2))
|
|
22
|
+
* **relay:** keep usage available for all retained devices ([#159](https://github.com/AksharP5/cohall/issues/159)) ([a01b960](https://github.com/AksharP5/cohall/commit/a01b960437011148059871fb46d0fa14ab40361d))
|
|
23
|
+
|
|
3
24
|
## [0.10.0](https://github.com/AksharP5/cohall/compare/v0.9.0...v0.10.0) (2026-10-04)
|
|
4
25
|
|
|
5
26
|
|
package/README.md
CHANGED
|
@@ -280,8 +280,12 @@ advertise attachment support. Named Grok Bots do not support file attachments.
|
|
|
280
280
|
If a long result and output files exceed the 1 MiB transfer limit, the task
|
|
281
281
|
completes with its text result and a notice that the files were omitted.
|
|
282
282
|
Provider text results must fit 128 KiB. Codex and OpenCode JSON events are capped
|
|
283
|
-
at 1 MiB each
|
|
284
|
-
|
|
283
|
+
at 1 MiB each; retained OpenCode message parts use the same bound. If an event
|
|
284
|
+
is discarded at that limit, the provider must return
|
|
285
|
+
a later complete answer or the task fails explicitly. Identified OpenCode
|
|
286
|
+
multipart answers must follow a new message-start event; more parts from the same
|
|
287
|
+
message cannot recover discarded content. Older OpenCode events without part IDs
|
|
288
|
+
keep their last-text behavior.
|
|
285
289
|
|
|
286
290
|
`--timeout` accepts 5 to 86400 seconds and defaults to 900. It limits how long
|
|
287
291
|
the command waits; the task continues after that. Invalid timeout values are
|
|
@@ -412,8 +416,9 @@ cohall usage
|
|
|
412
416
|
It and `cohall devices` include each device's current queue count and the
|
|
413
417
|
creation time of its oldest queued task. Assigned, running, cancelling, and
|
|
414
418
|
terminal tasks do not count as queued. Queue fields are absent with older relays.
|
|
415
|
-
`usage` reports retained Cohall task activity by device, status, and provider
|
|
416
|
-
|
|
419
|
+
`usage` reports retained Cohall task activity by device, status, and provider,
|
|
420
|
+
including forgotten devices whose tasks are still retained. Provider token counts
|
|
421
|
+
and billing are not available to the relay.
|
|
417
422
|
|
|
418
423
|
From the relay owner account, queue a Cohall upgrade for every registered
|
|
419
424
|
device and inspect progress:
|
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);
|
|
@@ -314,7 +314,7 @@ var init_src = __esm(() => {
|
|
|
314
314
|
retainedTasks: count,
|
|
315
315
|
byStatus: TaskStatusCounts,
|
|
316
316
|
byProvider: boundedArray(ProviderUsage, Provider.literals.length),
|
|
317
|
-
devices:
|
|
317
|
+
devices: Schema.Array(DeviceUsage)
|
|
318
318
|
});
|
|
319
319
|
TaskTraceEvent = Schema.Struct({
|
|
320
320
|
kind: TaskTraceEventKind,
|
|
@@ -421,9 +421,9 @@ var init_src = __esm(() => {
|
|
|
421
421
|
|
|
422
422
|
// apps/relay/src/config.ts
|
|
423
423
|
import { Effect as Effect13, Schema as Schema14 } from "effect";
|
|
424
|
-
import { access as access5, chmod as chmod7, mkdir as mkdir8, readFile as
|
|
424
|
+
import { access as access5, chmod as chmod7, mkdir as mkdir8, readFile as readFile4, writeFile as writeFile9 } from "node:fs/promises";
|
|
425
425
|
import { homedir as homedir5, platform as platform5 } from "node:os";
|
|
426
|
-
import { join as join9, resolve as resolve5 } from "node:path";
|
|
426
|
+
import { isAbsolute as isAbsolute6, join as join9, resolve as resolve5 } from "node:path";
|
|
427
427
|
var RelayConfiguration, defaultDataDirectory = () => {
|
|
428
428
|
if (platform5() === "win32") {
|
|
429
429
|
return join9(process.env.LOCALAPPDATA ?? join9(homedir5(), "AppData", "Local"), "Cohall");
|
|
@@ -431,10 +431,11 @@ var RelayConfiguration, defaultDataDirectory = () => {
|
|
|
431
431
|
if (platform5() === "darwin") {
|
|
432
432
|
return join9(homedir5(), "Library", "Application Support", "Cohall", "relay");
|
|
433
433
|
}
|
|
434
|
-
|
|
434
|
+
const dataHome = process.env.XDG_DATA_HOME;
|
|
435
|
+
return join9(dataHome !== undefined && isAbsolute6(dataHome) ? dataHome : join9(homedir5(), ".local", "share"), "cohall");
|
|
435
436
|
}, generatedToken = () => Array.from(crypto.getRandomValues(new Uint8Array(32)), (value) => value.toString(16).padStart(2, "0")).join(""), tokenFrom = async (path) => {
|
|
436
437
|
if (await access5(path).then(() => true).catch(() => false)) {
|
|
437
|
-
return (await
|
|
438
|
+
return (await readFile4(path, "utf8")).trim();
|
|
438
439
|
}
|
|
439
440
|
const token = generatedToken();
|
|
440
441
|
await writeFile9(path, `${token}
|
|
@@ -2712,6 +2713,14 @@ Content-Length: 0\r
|
|
|
2712
2713
|
return yield* store.exchangePairing(input.token);
|
|
2713
2714
|
})).then((value) => json(value, 201)).catch(() => json({ error: "Pairing credential is invalid, expired, or already used" }, 401));
|
|
2714
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
|
+
}
|
|
2715
2724
|
const attachmentRoute = request.method === "GET" && /^\/api\/tasks\/[^/]+\/attachments(?:\/[^/]+)?$/.test(url.pathname);
|
|
2716
2725
|
const progressRoute = request.method === "POST" && /^\/api\/tasks\/[^/]+\/progress$/.test(url.pathname);
|
|
2717
2726
|
const inputRoute = request.method === "POST" && /^\/api\/tasks\/[^/]+\/input$/.test(url.pathname);
|
|
@@ -3173,6 +3182,7 @@ var make = (options) => {
|
|
|
3173
3182
|
});
|
|
3174
3183
|
const devicePage = (after) => request("RelayClient.devices", `/api/devices/page${after === undefined ? "" : `?after=${encodeURIComponent(after)}`}`, DevicePage, undefined, maxDevicePageResponseBytes);
|
|
3175
3184
|
return Service.of({
|
|
3185
|
+
verifyDeviceCredential: () => request("RelayClient.verifyDeviceCredential", "/api/device/credential", Schema2.Struct({ ok: Schema2.Literal(true) })).pipe(Effect2.asVoid),
|
|
3176
3186
|
devices: () => Effect2.gen(function* () {
|
|
3177
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)));
|
|
3178
3188
|
if (!("devices" in first))
|
|
@@ -3316,29 +3326,30 @@ var make = (options) => {
|
|
|
3316
3326
|
};
|
|
3317
3327
|
var exchangePairing = (baseUrl, input) => Effect2.gen(function* () {
|
|
3318
3328
|
const operation = "RelayClient.exchangePairing";
|
|
3319
|
-
const
|
|
3320
|
-
try: (signal) =>
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
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({
|
|
3327
3349
|
operation,
|
|
3328
3350
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
3329
3351
|
})
|
|
3330
3352
|
});
|
|
3331
|
-
if (!response.ok) {
|
|
3332
|
-
return yield* Effect2.fail(new RelayRequestError({
|
|
3333
|
-
operation,
|
|
3334
|
-
message: yield* Effect2.promise(() => responseMessage(response, operation)),
|
|
3335
|
-
status: response.status
|
|
3336
|
-
}));
|
|
3337
|
-
}
|
|
3338
|
-
const json = yield* Effect2.tryPromise({
|
|
3339
|
-
try: () => jsonBody(response, operation),
|
|
3340
|
-
catch: (cause) => cause instanceof RelayDecodeError ? cause : new RelayDecodeError({ operation, message: String(cause) })
|
|
3341
|
-
});
|
|
3342
3353
|
return yield* Schema2.decodeUnknownEffect(PairingResult)(json).pipe(Effect2.mapError((cause) => new RelayDecodeError({ operation, message: String(cause) })));
|
|
3343
3354
|
});
|
|
3344
3355
|
var layer = (options) => Layer.succeed(Service, make(options));
|
|
@@ -3365,6 +3376,8 @@ class ProviderRunError extends Schema3.TaggedErrorClass()("CohallProvider.RunErr
|
|
|
3365
3376
|
exitCode: Schema3.optionalKey(Schema3.Number)
|
|
3366
3377
|
}) {
|
|
3367
3378
|
}
|
|
3379
|
+
var maxResultBytes = 131072;
|
|
3380
|
+
var maxJsonBytes = 1024 * 1024;
|
|
3368
3381
|
var executables = {
|
|
3369
3382
|
codex: "codex",
|
|
3370
3383
|
"claude-code": "claude",
|
|
@@ -3541,7 +3554,6 @@ var prepareCommand = async (options) => {
|
|
|
3541
3554
|
}
|
|
3542
3555
|
};
|
|
3543
3556
|
var forEachJsonEvent = async (stdout, onEvent, onOversized) => {
|
|
3544
|
-
const limit = 1024 * 1024;
|
|
3545
3557
|
let chunks = [];
|
|
3546
3558
|
let size = 0;
|
|
3547
3559
|
let oversized = false;
|
|
@@ -3549,7 +3561,7 @@ var forEachJsonEvent = async (stdout, onEvent, onOversized) => {
|
|
|
3549
3561
|
if (oversized) {
|
|
3550
3562
|
return;
|
|
3551
3563
|
}
|
|
3552
|
-
if (size + chunk.byteLength >
|
|
3564
|
+
if (size + chunk.byteLength > maxJsonBytes) {
|
|
3553
3565
|
chunks = [];
|
|
3554
3566
|
size = 0;
|
|
3555
3567
|
oversized = true;
|
|
@@ -3653,25 +3665,69 @@ var parseOpenCode = async (stdout, existingSession) => {
|
|
|
3653
3665
|
let result = "";
|
|
3654
3666
|
let sessionId = existingSession;
|
|
3655
3667
|
let discardedEvent = false;
|
|
3668
|
+
let messageId;
|
|
3669
|
+
const parts = new Map;
|
|
3670
|
+
let partBytes = 0;
|
|
3671
|
+
let oversizedMessage = false;
|
|
3672
|
+
const selectMessage = (id) => {
|
|
3673
|
+
messageId = id;
|
|
3674
|
+
parts.clear();
|
|
3675
|
+
partBytes = 0;
|
|
3676
|
+
oversizedMessage = false;
|
|
3677
|
+
result = "";
|
|
3678
|
+
};
|
|
3656
3679
|
await forEachJsonEvent(stdout, (event) => {
|
|
3657
3680
|
const failure = openCodeError(event);
|
|
3658
3681
|
if (failure !== undefined) {
|
|
3659
3682
|
throw new Error(failure);
|
|
3660
3683
|
}
|
|
3661
3684
|
sessionId = text(event.sessionID) ?? text(event.sessionId) ?? text(event.session_id) ?? text(record(event.info)?.sessionID) ?? sessionId;
|
|
3685
|
+
const part = record(event.part);
|
|
3686
|
+
const nextMessageId = text(part?.messageID);
|
|
3687
|
+
if (text(event.type) === "step_start" && nextMessageId !== undefined && nextMessageId !== messageId) {
|
|
3688
|
+
selectMessage(nextMessageId);
|
|
3689
|
+
discardedEvent = false;
|
|
3690
|
+
return;
|
|
3691
|
+
}
|
|
3662
3692
|
if (text(event.type) !== "text")
|
|
3663
3693
|
return;
|
|
3664
|
-
const content = textFromPart(
|
|
3665
|
-
if (content
|
|
3666
|
-
|
|
3667
|
-
|
|
3694
|
+
const content = textFromPart(part) ?? textFromPart(event) ?? text(event.result);
|
|
3695
|
+
if (content === undefined)
|
|
3696
|
+
return;
|
|
3697
|
+
const partId = text(part?.id);
|
|
3698
|
+
if (nextMessageId !== undefined && partId !== undefined) {
|
|
3699
|
+
if (nextMessageId !== messageId)
|
|
3700
|
+
selectMessage(nextMessageId);
|
|
3701
|
+
if (oversizedMessage)
|
|
3702
|
+
return;
|
|
3703
|
+
const previous = parts.get(partId);
|
|
3704
|
+
if (content.length === 0 && previous === undefined)
|
|
3705
|
+
return;
|
|
3706
|
+
partBytes += byteLength(content) - byteLength(previous ?? "") + (previous === undefined ? byteLength(partId) : 0);
|
|
3707
|
+
if (partBytes > maxJsonBytes) {
|
|
3708
|
+
oversizedMessage = true;
|
|
3709
|
+
parts.clear();
|
|
3710
|
+
return;
|
|
3711
|
+
}
|
|
3712
|
+
parts.set(partId, content);
|
|
3713
|
+
return;
|
|
3668
3714
|
}
|
|
3715
|
+
if (content.length === 0)
|
|
3716
|
+
return;
|
|
3717
|
+
selectMessage(undefined);
|
|
3718
|
+
result = content;
|
|
3719
|
+
discardedEvent = false;
|
|
3669
3720
|
}, () => {
|
|
3670
3721
|
discardedEvent = true;
|
|
3671
3722
|
});
|
|
3672
3723
|
if (discardedEvent) {
|
|
3673
3724
|
throw new Error("Provider JSON event exceeded 1 MiB without a later text response");
|
|
3674
3725
|
}
|
|
3726
|
+
if (oversizedMessage)
|
|
3727
|
+
throw new Error("OpenCode message exceeded 1 MiB");
|
|
3728
|
+
if (parts.size > 0)
|
|
3729
|
+
result = [...parts.values()].filter((content) => content.length > 0).join(`
|
|
3730
|
+
`);
|
|
3675
3731
|
if (result.length === 0) {
|
|
3676
3732
|
throw new Error("OpenCode produced no result; verify its authentication and use a supported project workspace");
|
|
3677
3733
|
}
|
|
@@ -3683,7 +3739,7 @@ var parse = async (options, stdout, onFailure) => {
|
|
|
3683
3739
|
case "codex":
|
|
3684
3740
|
return await parseCodex(stdout, options.sessionId);
|
|
3685
3741
|
case "claude-code":
|
|
3686
|
-
return parseClaude(await boundedText(stdout,
|
|
3742
|
+
return parseClaude(await boundedText(stdout, maxJsonBytes, onFailure), options.sessionId);
|
|
3687
3743
|
case "opencode":
|
|
3688
3744
|
return await parseOpenCode(stdout, options.sessionId);
|
|
3689
3745
|
}
|
|
@@ -3808,7 +3864,7 @@ var run = (options) => Effect3.callback((resume, signal) => {
|
|
|
3808
3864
|
exitCode
|
|
3809
3865
|
});
|
|
3810
3866
|
}
|
|
3811
|
-
if (byteLength(result.result) >
|
|
3867
|
+
if (byteLength(result.result) > maxResultBytes) {
|
|
3812
3868
|
throw new ProviderRunError({
|
|
3813
3869
|
provider: options.provider,
|
|
3814
3870
|
message: "Provider result exceeded 128 KiB"
|
|
@@ -3848,7 +3904,7 @@ var run = (options) => Effect3.callback((resume, signal) => {
|
|
|
3848
3904
|
|
|
3849
3905
|
// apps/device/src/cli.ts
|
|
3850
3906
|
import { Effect as Effect10, Schema as Schema11 } from "effect";
|
|
3851
|
-
import { access as access4, mkdir as mkdir7,
|
|
3907
|
+
import { access as access4, mkdir as mkdir7, open as open4, stat as stat4, writeFile as writeFile7 } from "node:fs/promises";
|
|
3852
3908
|
import { homedir as homedir4 } from "node:os";
|
|
3853
3909
|
import { dirname as dirname6, join as join8, resolve as resolve4 } from "node:path";
|
|
3854
3910
|
|
|
@@ -4121,7 +4177,7 @@ init_src();
|
|
|
4121
4177
|
import { Effect as Effect4, Schema as Schema4 } from "effect";
|
|
4122
4178
|
import { access, chmod as chmod2, mkdir, readFile, realpath, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
4123
4179
|
import { homedir as homedir2, hostname, platform as platform2 } from "node:os";
|
|
4124
|
-
import { dirname, join as join2, resolve } from "node:path";
|
|
4180
|
+
import { dirname, isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
|
|
4125
4181
|
var WorkspaceList = Schema4.Array(Schema4.NonEmptyString).check(Schema4.isMaxLength(64));
|
|
4126
4182
|
var ProviderList = Schema4.Array(Provider).check(Schema4.isMinLength(1)).check(Schema4.isMaxLength(Provider.literals.length));
|
|
4127
4183
|
var Sandbox = Schema4.Literals(["read-only", "workspace-write", "danger-full-access"]);
|
|
@@ -4163,7 +4219,8 @@ var defaultConfigDirectory = () => {
|
|
|
4163
4219
|
if (platform2() === "darwin") {
|
|
4164
4220
|
return join2(homedir2(), "Library", "Application Support", "Cohall");
|
|
4165
4221
|
}
|
|
4166
|
-
|
|
4222
|
+
const configHome = process.env.XDG_CONFIG_HOME;
|
|
4223
|
+
return join2(configHome !== undefined && isAbsolute2(configHome) ? configHome : join2(homedir2(), ".config"), "cohall");
|
|
4167
4224
|
};
|
|
4168
4225
|
var defaultRelayDataDirectory = () => {
|
|
4169
4226
|
if (platform2() === "win32") {
|
|
@@ -4172,7 +4229,8 @@ var defaultRelayDataDirectory = () => {
|
|
|
4172
4229
|
if (platform2() === "darwin") {
|
|
4173
4230
|
return join2(homedir2(), "Library", "Application Support", "Cohall", "relay");
|
|
4174
4231
|
}
|
|
4175
|
-
|
|
4232
|
+
const dataHome = process.env.XDG_DATA_HOME;
|
|
4233
|
+
return join2(dataHome !== undefined && isAbsolute2(dataHome) ? dataHome : join2(homedir2(), ".local", "share"), "cohall");
|
|
4176
4234
|
};
|
|
4177
4235
|
var relayDataDirectory = () => resolve(process.env.COHALL_DATA_DIR ?? defaultRelayDataDirectory());
|
|
4178
4236
|
var configurationPath = () => resolve(process.env.COHALL_CONFIG ?? join2(defaultConfigDirectory(), "config.json"));
|
|
@@ -5237,7 +5295,7 @@ import { WebSocket } from "ws";
|
|
|
5237
5295
|
import { execFile as execFile2 } from "node:child_process";
|
|
5238
5296
|
import { chmod as chmod5, mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
|
|
5239
5297
|
import { homedir as homedir3 } from "node:os";
|
|
5240
|
-
import { dirname as dirname4, isAbsolute as
|
|
5298
|
+
import { dirname as dirname4, isAbsolute as isAbsolute4, join as join5 } from "node:path";
|
|
5241
5299
|
|
|
5242
5300
|
// apps/device/src/upgrade.ts
|
|
5243
5301
|
import { Schema as Schema8 } from "effect";
|
|
@@ -5255,7 +5313,7 @@ import {
|
|
|
5255
5313
|
stat as stat2,
|
|
5256
5314
|
writeFile as writeFile3
|
|
5257
5315
|
} from "node:fs/promises";
|
|
5258
|
-
import { delimiter as delimiter2, dirname as dirname3, extname as extname2, isAbsolute as
|
|
5316
|
+
import { delimiter as delimiter2, dirname as dirname3, extname as extname2, isAbsolute as isAbsolute3, join as join4, relative, resolve as resolve2 } from "node:path";
|
|
5259
5317
|
import { platform as operatingSystem } from "node:os";
|
|
5260
5318
|
var packageName = "@akshar5/cohall";
|
|
5261
5319
|
var minimumBunLookupVersion = "1.2.15";
|
|
@@ -5383,7 +5441,7 @@ var resolvePackageInstallation = async (entrypoint, options = {}) => {
|
|
|
5383
5441
|
if (bun !== undefined) {
|
|
5384
5442
|
const result2 = await (options.runner ?? defaultRunner).run(bun, ["pm", "ls", "--global"], 1e4);
|
|
5385
5443
|
const root2 = result2.stdout.split(/\r?\n/, 1)[0]?.match(/^(.*?) node_modules \(\d+(?: installed)?\)$/)?.[1];
|
|
5386
|
-
if (result2.exitCode === 0 && root2 !== undefined &&
|
|
5444
|
+
if (result2.exitCode === 0 && root2 !== undefined && isAbsolute3(root2) && await realpath2(join4(root2, "node_modules", packageName, "bin", "cohall.js")).catch(() => {
|
|
5387
5445
|
return;
|
|
5388
5446
|
}) === canonicalEntrypoint)
|
|
5389
5447
|
return {
|
|
@@ -5412,7 +5470,7 @@ var resolvePackageInstallation = async (entrypoint, options = {}) => {
|
|
|
5412
5470
|
const reported = normalizePath(result.stdout.trim().split(/\r?\n/).at(-1)?.trim() ?? "");
|
|
5413
5471
|
const root = reported.endsWith("/node_modules") ? dirname3(reported) : reported;
|
|
5414
5472
|
const selected = resolve2(globalRoot);
|
|
5415
|
-
const configured =
|
|
5473
|
+
const configured = isAbsolute3(root) ? resolve2(root) : undefined;
|
|
5416
5474
|
if (configured === undefined || (process.platform === "win32" ? selected.toLowerCase() !== configured.toLowerCase() : selected !== configured))
|
|
5417
5475
|
throw globalEntrypointError();
|
|
5418
5476
|
const packageEntry = (root2) => join4(root2, "node_modules", packageName, "bin", "cohall.js");
|
|
@@ -5440,7 +5498,7 @@ var pnpmServiceDirectories = async (executable, globalRoot, runner = defaultRunn
|
|
|
5440
5498
|
const query = async (arguments_) => {
|
|
5441
5499
|
const result = await checked(runner, { command: executable, arguments: arguments_, environment: pnpmEnvironment() }, 1e4);
|
|
5442
5500
|
const directory = result.stdout.trim().split(/\r?\n/).at(-1)?.trim() ?? "";
|
|
5443
|
-
if (!
|
|
5501
|
+
if (!isAbsolute3(directory))
|
|
5444
5502
|
throw new Error(`pnpm ${arguments_.join(" ")} did not report an absolute directory`);
|
|
5445
5503
|
return directory;
|
|
5446
5504
|
};
|
|
@@ -5491,7 +5549,7 @@ var executableNames = (command) => {
|
|
|
5491
5549
|
};
|
|
5492
5550
|
var containsPath = (root, path) => {
|
|
5493
5551
|
const child = relative(root, path);
|
|
5494
|
-
return child === "" || !child.startsWith("..") && !
|
|
5552
|
+
return child === "" || !child.startsWith("..") && !isAbsolute3(child);
|
|
5495
5553
|
};
|
|
5496
5554
|
var linuxSystemExecutableRoots = [
|
|
5497
5555
|
"/bin",
|
|
@@ -5543,7 +5601,7 @@ var validateExecutable = async (candidate, installationRoot, preserveSymlink = f
|
|
|
5543
5601
|
return preserveSymlink ? resolve2(candidate) : canonical;
|
|
5544
5602
|
};
|
|
5545
5603
|
var trustedExecutable = async (command, options = {}) => {
|
|
5546
|
-
const candidates =
|
|
5604
|
+
const candidates = isAbsolute3(command) ? [command] : (process.env.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((directory) => executableNames(command).map((name) => join4(directory, name)));
|
|
5547
5605
|
let failure;
|
|
5548
5606
|
for (const path of candidates) {
|
|
5549
5607
|
const available2 = await access2(path, operatingSystem() === "win32" ? constants2.F_OK : constants2.X_OK).then(() => true).catch(() => false);
|
|
@@ -6044,7 +6102,7 @@ var deviceServicePlan = (options) => {
|
|
|
6044
6102
|
].join(":");
|
|
6045
6103
|
if (options.platform === "linux") {
|
|
6046
6104
|
const configured = process.env.XDG_CONFIG_HOME;
|
|
6047
|
-
const configHome = configured !== undefined &&
|
|
6105
|
+
const configHome = configured !== undefined && isAbsolute4(configured) ? configured : join5(options.home, ".config");
|
|
6048
6106
|
const path = join5(configHome, "systemd", "user", "cohall-device.service");
|
|
6049
6107
|
return {
|
|
6050
6108
|
file: {
|
|
@@ -6441,7 +6499,7 @@ var isLoopback = (url) => url.hostname === "localhost" || url.hostname === "[::1
|
|
|
6441
6499
|
var verifyClientCredential = async (relayUrl, token) => {
|
|
6442
6500
|
await Effect8.runPromise(exports_src.make({ baseUrl: relayUrl, token }).devices());
|
|
6443
6501
|
};
|
|
6444
|
-
var
|
|
6502
|
+
var verifyLegacyDeviceCredential = (relayUrl, token) => new Promise((resolvePromise, reject) => {
|
|
6445
6503
|
const socket = new WebSocket(websocketUrl(relayUrl), {
|
|
6446
6504
|
handshakeTimeout: 1e4,
|
|
6447
6505
|
maxPayload: 1024 * 1024,
|
|
@@ -6480,6 +6538,12 @@ var verifyDeviceCredential = (relayUrl, token) => new Promise((resolvePromise, r
|
|
|
6480
6538
|
socket.once("error", (cause) => finish(cause));
|
|
6481
6539
|
socket.once("close", () => finish(new Error("New relay rejected the stored device credential")));
|
|
6482
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
|
+
});
|
|
6483
6547
|
var switchRelay = async (options) => {
|
|
6484
6548
|
if (process.env.COHALL_RELAY_URL !== undefined) {
|
|
6485
6549
|
throw new Error("Unset COHALL_RELAY_URL before changing the stored relay URL");
|
|
@@ -6536,33 +6600,29 @@ var switchRelay = async (options) => {
|
|
|
6536
6600
|
init_src();
|
|
6537
6601
|
import { Effect as Effect9, Schema as Schema10 } from "effect";
|
|
6538
6602
|
import { constants as constants3 } from "node:fs";
|
|
6539
|
-
import {
|
|
6540
|
-
lstat as lstat2,
|
|
6541
|
-
mkdtemp as mkdtemp3,
|
|
6542
|
-
mkdir as mkdir6,
|
|
6543
|
-
open as open3,
|
|
6544
|
-
readdir as readdir2,
|
|
6545
|
-
rm as rm4,
|
|
6546
|
-
writeFile as writeFile6
|
|
6547
|
-
} from "node:fs/promises";
|
|
6603
|
+
import { lstat as lstat2, mkdtemp as mkdtemp3, mkdir as mkdir6, open as open3, readdir as readdir2, rm as rm4, writeFile as writeFile6 } from "node:fs/promises";
|
|
6548
6604
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
6549
6605
|
import { basename as basename2, join as join7 } from "node:path";
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6606
|
+
|
|
6607
|
+
// apps/device/src/bounded-file.ts
|
|
6608
|
+
var readBoundedFile = async (handle, limit) => {
|
|
6609
|
+
const buffer = Buffer.allocUnsafe(limit + 1);
|
|
6610
|
+
let size = 0;
|
|
6611
|
+
while (size < buffer.length) {
|
|
6554
6612
|
const { bytesRead } = await handle.read({
|
|
6555
6613
|
buffer,
|
|
6556
|
-
offset,
|
|
6557
|
-
length: buffer.length -
|
|
6614
|
+
offset: size,
|
|
6615
|
+
length: buffer.length - size,
|
|
6558
6616
|
position: null
|
|
6559
6617
|
});
|
|
6560
6618
|
if (bytesRead === 0)
|
|
6561
6619
|
break;
|
|
6562
|
-
|
|
6620
|
+
size += bytesRead;
|
|
6563
6621
|
}
|
|
6564
|
-
return buffer.subarray(0,
|
|
6622
|
+
return buffer.subarray(0, size);
|
|
6565
6623
|
};
|
|
6624
|
+
|
|
6625
|
+
// apps/device/src/task-attachments.ts
|
|
6566
6626
|
var readInputAttachments = async (paths2) => {
|
|
6567
6627
|
if (paths2.length > maxTaskAttachments) {
|
|
6568
6628
|
throw new Error(`A task supports at most ${maxTaskAttachments} input files`);
|
|
@@ -6579,13 +6639,13 @@ var readInputAttachments = async (paths2) => {
|
|
|
6579
6639
|
if (!source.isFile()) {
|
|
6580
6640
|
throw new Error(`Attachment ${name} must be a regular file`);
|
|
6581
6641
|
}
|
|
6582
|
-
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));
|
|
6583
6643
|
try {
|
|
6584
6644
|
const metadata = await handle.stat();
|
|
6585
6645
|
if (!metadata.isFile() || metadata.dev !== source.dev || metadata.ino !== source.ino || metadata.size < 1 || metadata.size > maxAttachmentBytes) {
|
|
6586
6646
|
throw new Error(`Attachment ${name} must be a regular file of 1 to ${maxAttachmentBytes} bytes`);
|
|
6587
6647
|
}
|
|
6588
|
-
const data = await readBoundedFile(handle);
|
|
6648
|
+
const data = await readBoundedFile(handle, maxAttachmentBytes);
|
|
6589
6649
|
if (data.length < 1 || data.length > maxAttachmentBytes) {
|
|
6590
6650
|
throw new Error(`Attachment ${name} changed size while reading`);
|
|
6591
6651
|
}
|
|
@@ -6636,14 +6696,14 @@ var collectOutputs = async (directory) => {
|
|
|
6636
6696
|
throw new Error(`Output ${name} repeats another file name`);
|
|
6637
6697
|
}
|
|
6638
6698
|
names.add(key);
|
|
6639
|
-
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));
|
|
6640
6700
|
try {
|
|
6641
6701
|
const metadata = await handle.stat();
|
|
6642
6702
|
const pathMetadata = await lstat2(join7(directory, name));
|
|
6643
6703
|
if (!metadata.isFile() || !pathMetadata.isFile() || metadata.dev !== pathMetadata.dev || metadata.ino !== pathMetadata.ino || metadata.size < 1 || metadata.size > maxAttachmentBytes) {
|
|
6644
6704
|
throw new Error(`Output ${name} must be a regular file of 1 to ${maxAttachmentBytes} bytes`);
|
|
6645
6705
|
}
|
|
6646
|
-
const data = await readBoundedFile(handle);
|
|
6706
|
+
const data = await readBoundedFile(handle, maxAttachmentBytes);
|
|
6647
6707
|
if (data.length < 1 || data.length > maxAttachmentBytes) {
|
|
6648
6708
|
throw new Error(`Output ${name} changed size while reading`);
|
|
6649
6709
|
}
|
|
@@ -6867,6 +6927,19 @@ var identifier = (arguments_, label) => {
|
|
|
6867
6927
|
};
|
|
6868
6928
|
var print = (value) => console.log(JSON.stringify(value, null, 2));
|
|
6869
6929
|
var printLine = (value) => console.log(JSON.stringify(value));
|
|
6930
|
+
var readTextFile = async (path, limit, exceeded) => {
|
|
6931
|
+
if ((await stat4(path)).size > limit)
|
|
6932
|
+
throw new Error(exceeded);
|
|
6933
|
+
const file = await open4(path, "r");
|
|
6934
|
+
try {
|
|
6935
|
+
const data = await readBoundedFile(file, limit);
|
|
6936
|
+
if (data.length > limit)
|
|
6937
|
+
throw new Error(exceeded);
|
|
6938
|
+
return data.toString("utf8");
|
|
6939
|
+
} finally {
|
|
6940
|
+
await file.close();
|
|
6941
|
+
}
|
|
6942
|
+
};
|
|
6870
6943
|
var readInput = async (arguments_, name) => {
|
|
6871
6944
|
const direct = option(arguments_, name);
|
|
6872
6945
|
const path = option(arguments_, `${name}-file`);
|
|
@@ -6878,11 +6951,8 @@ var readInput = async (arguments_, name) => {
|
|
|
6878
6951
|
if (!await access4(path).then(() => true).catch(() => false)) {
|
|
6879
6952
|
throw new Error(`File does not exist: ${path}`);
|
|
6880
6953
|
}
|
|
6881
|
-
if ((await stat4(path)).size > byteLimit) {
|
|
6882
|
-
throw new Error(`${name} file exceeds ${byteLimit / 1024} KiB`);
|
|
6883
|
-
}
|
|
6884
6954
|
}
|
|
6885
|
-
const result = path === undefined ? direct === "-" ? await readStdin(byteLimit, name) : direct : await
|
|
6955
|
+
const result = path === undefined ? direct === "-" ? await readStdin(byteLimit, name) : direct : await readTextFile(path, byteLimit, `${name} file exceeds ${byteLimit / 1024} KiB`);
|
|
6886
6956
|
if (result !== undefined && result.length > 131072) {
|
|
6887
6957
|
throw new Error(`${name} exceeds 131072 characters`);
|
|
6888
6958
|
}
|
|
@@ -6913,10 +6983,7 @@ var pairingToken = async (arguments_) => {
|
|
|
6913
6983
|
if (path !== undefined && !await access4(path).then(() => true).catch(() => false)) {
|
|
6914
6984
|
throw new Error(`Pairing token file does not exist: ${path}`);
|
|
6915
6985
|
}
|
|
6916
|
-
|
|
6917
|
-
throw new Error("Pairing token file exceeds 512 bytes");
|
|
6918
|
-
}
|
|
6919
|
-
const token = (path === undefined ? await readStdin(512, "Pairing token") : await readFile4(path, "utf8")).trim();
|
|
6986
|
+
const token = (path === undefined ? await readStdin(512, "Pairing token") : await readTextFile(path, 512, "Pairing token file exceeds 512 bytes")).trim();
|
|
6920
6987
|
if (token.length === 0 || new TextEncoder().encode(token).byteLength > 256) {
|
|
6921
6988
|
throw new Error("Provide a pairing token on stdin or with --token-file");
|
|
6922
6989
|
}
|
|
@@ -7590,9 +7657,9 @@ var runCli = async (command, raw) => {
|
|
|
7590
7657
|
init_src();
|
|
7591
7658
|
import { Effect as Effect11, Schedule as Schedule3, Schema as Schema12 } from "effect";
|
|
7592
7659
|
import { constants as constants4 } from "node:fs";
|
|
7593
|
-
import { open as
|
|
7660
|
+
import { open as open5, realpath as realpath4, stat as stat5 } from "node:fs/promises";
|
|
7594
7661
|
import { arch, hostname as hostname3, platform as platform4 } from "node:os";
|
|
7595
|
-
import { basename as basename3, isAbsolute as
|
|
7662
|
+
import { basename as basename3, isAbsolute as isAbsolute5, relative as relative2, sep } from "node:path";
|
|
7596
7663
|
import { WebSocket as WebSocket2 } from "ws";
|
|
7597
7664
|
var maxQueuedRelayMessages = 8;
|
|
7598
7665
|
|
|
@@ -7673,7 +7740,7 @@ var allowedWorkspace = async (configuration, requested) => {
|
|
|
7673
7740
|
const candidate = await realpath4(selected);
|
|
7674
7741
|
const allowed = configuration.workspaces.some((root) => {
|
|
7675
7742
|
const child = relative2(root, candidate);
|
|
7676
|
-
return child
|
|
7743
|
+
return child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute5(child);
|
|
7677
7744
|
});
|
|
7678
7745
|
if (!allowed) {
|
|
7679
7746
|
throw new Error(`Workspace ${candidate} is outside this device's configured workspace roots`);
|
|
@@ -7684,7 +7751,7 @@ var openAllowedWorkspace = async (configuration, requested) => {
|
|
|
7684
7751
|
const path = await allowedWorkspace(configuration, requested);
|
|
7685
7752
|
const operatingSystem2 = platform4();
|
|
7686
7753
|
const flags = constants4.O_RDONLY | (operatingSystem2 === "win32" ? 0 : constants4.O_DIRECTORY | constants4.O_NOFOLLOW);
|
|
7687
|
-
const handle = await
|
|
7754
|
+
const handle = await open5(path, flags);
|
|
7688
7755
|
const identity = await handle.stat();
|
|
7689
7756
|
if (!identity.isDirectory()) {
|
|
7690
7757
|
await handle.close();
|
|
@@ -8395,11 +8462,12 @@ var runMcp = async (configuration) => {
|
|
|
8395
8462
|
output_path: z.string(),
|
|
8396
8463
|
direction: z.enum(["input", "output"]).optional()
|
|
8397
8464
|
}
|
|
8398
|
-
}, async ({ task_id, name, output_path, direction }) => {
|
|
8465
|
+
}, async ({ task_id, name, output_path, direction }, { signal }) => {
|
|
8399
8466
|
const safeName = Schema13.decodeUnknownSync(AttachmentName)(name);
|
|
8400
8467
|
const safeDirection = direction === undefined ? undefined : Schema13.decodeUnknownSync(AttachmentDirection)(direction);
|
|
8401
|
-
const data = await Effect12.runPromise(client2.readAttachment(TaskId.make(task_id), safeName, safeDirection));
|
|
8402
|
-
|
|
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 });
|
|
8403
8471
|
return output({ task_id, name: safeName, output_path, bytes: data.length });
|
|
8404
8472
|
});
|
|
8405
8473
|
server.registerTool("task_trace", {
|
|
@@ -8485,4 +8553,4 @@ await main().catch((cause) => {
|
|
|
8485
8553
|
process.exitCode = 1;
|
|
8486
8554
|
});
|
|
8487
8555
|
|
|
8488
|
-
//# debugId=
|
|
8556
|
+
//# debugId=49CCA945DC24F88C64756E2164756E21
|