@akshar5/cohall 0.3.4 → 0.4.1
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 +23 -0
- package/README.md +6 -0
- package/bin/cohall.js +592 -173
- package/bin/cohall.js.map +8 -7
- package/docs/install.md +17 -4
- package/docs/services.md +13 -0
- package/package.json +1 -1
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 { Schema } from "effect";
|
|
20
|
-
var version = "0.
|
|
20
|
+
var version = "0.4.1", 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)), 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)), DeviceId, ThreadId, MessageId, TaskId, AuthSessionId, Timestamp, Platform, Provider, DeviceStatus, TaskStatus, TaskTraceEventKind, MessageRole, ConnectionRole, AuthSession, CreatePairingInput, PairingCredential, ExchangePairingInput, SessionCredential, PairingResult, Capability, Workspace, Device, Thread, Message, Task, 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()), decodeCreatePairingInput, decodeExchangePairingInput, decodeCreateTaskInput, decodeDevice, decodeSocketEvent;
|
|
23
23
|
var init_src = __esm(() => {
|
|
@@ -201,27 +201,27 @@ var init_src = __esm(() => {
|
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
// apps/relay/src/config.ts
|
|
204
|
-
import { Effect as Effect8, Schema as
|
|
205
|
-
import { access as access3, chmod as
|
|
204
|
+
import { Effect as Effect8, Schema as Schema9 } from "effect";
|
|
205
|
+
import { access as access3, chmod as chmod4, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
|
|
206
206
|
import { homedir as homedir4, platform as platform4 } from "node:os";
|
|
207
|
-
import { join as
|
|
207
|
+
import { join as join5, resolve as resolve2 } from "node:path";
|
|
208
208
|
var RelayConfiguration, defaultDataDirectory = () => {
|
|
209
209
|
if (platform4() === "win32") {
|
|
210
|
-
return
|
|
210
|
+
return join5(process.env.LOCALAPPDATA ?? join5(homedir4(), "AppData", "Local"), "Cohall");
|
|
211
211
|
}
|
|
212
212
|
if (platform4() === "darwin") {
|
|
213
|
-
return
|
|
213
|
+
return join5(homedir4(), "Library", "Application Support", "Cohall", "relay");
|
|
214
214
|
}
|
|
215
|
-
return
|
|
215
|
+
return join5(process.env.XDG_DATA_HOME ?? join5(homedir4(), ".local", "share"), "cohall");
|
|
216
216
|
}, generatedToken = () => Array.from(crypto.getRandomValues(new Uint8Array(32)), (value) => value.toString(16).padStart(2, "0")).join(""), tokenFrom = async (path) => {
|
|
217
217
|
if (await access3(path).then(() => true).catch(() => false)) {
|
|
218
|
-
return (await
|
|
218
|
+
return (await readFile4(path, "utf8")).trim();
|
|
219
219
|
}
|
|
220
220
|
const token = generatedToken();
|
|
221
|
-
await
|
|
221
|
+
await writeFile5(path, `${token}
|
|
222
222
|
`, { mode: 384 });
|
|
223
223
|
if (platform4() !== "win32") {
|
|
224
|
-
await
|
|
224
|
+
await chmod4(path, 384);
|
|
225
225
|
}
|
|
226
226
|
return token;
|
|
227
227
|
}, booleanEnvironment = (name) => {
|
|
@@ -235,13 +235,13 @@ var RelayConfiguration, defaultDataDirectory = () => {
|
|
|
235
235
|
throw new Error(`${name} must be true or false`);
|
|
236
236
|
}, isLoopback = (host) => host === "127.0.0.1" || host === "localhost" || host === "::1", loadEnvironmentConfiguration;
|
|
237
237
|
var init_config = __esm(() => {
|
|
238
|
-
RelayConfiguration =
|
|
239
|
-
host:
|
|
240
|
-
port:
|
|
241
|
-
token:
|
|
242
|
-
dataDirectory:
|
|
243
|
-
databasePath:
|
|
244
|
-
allowRemote:
|
|
238
|
+
RelayConfiguration = Schema9.Struct({
|
|
239
|
+
host: Schema9.NonEmptyString,
|
|
240
|
+
port: Schema9.Int.check(Schema9.isBetween({ minimum: 1, maximum: 65535 })),
|
|
241
|
+
token: Schema9.NonEmptyString,
|
|
242
|
+
dataDirectory: Schema9.NonEmptyString,
|
|
243
|
+
databasePath: Schema9.NonEmptyString,
|
|
244
|
+
allowRemote: Schema9.Boolean
|
|
245
245
|
});
|
|
246
246
|
loadEnvironmentConfiguration = Effect8.tryPromise({
|
|
247
247
|
try: async () => {
|
|
@@ -252,17 +252,17 @@ var init_config = __esm(() => {
|
|
|
252
252
|
throw new Error("Remote relay binding is disabled. Use a private network or TLS reverse proxy, then set COHALL_RELAY_ALLOW_REMOTE=true.");
|
|
253
253
|
}
|
|
254
254
|
const dataDirectory = resolve2(process.env.COHALL_DATA_DIR ?? defaultDataDirectory());
|
|
255
|
-
await
|
|
255
|
+
await mkdir4(dataDirectory, { recursive: true, mode: 448 });
|
|
256
256
|
if (platform4() !== "win32") {
|
|
257
|
-
await
|
|
257
|
+
await chmod4(dataDirectory, 448);
|
|
258
258
|
}
|
|
259
|
-
const token = process.env.COHALL_TOKEN ?? await tokenFrom(
|
|
260
|
-
return
|
|
259
|
+
const token = process.env.COHALL_TOKEN ?? await tokenFrom(join5(dataDirectory, "owner-token"));
|
|
260
|
+
return Schema9.decodeUnknownSync(RelayConfiguration)({
|
|
261
261
|
host,
|
|
262
262
|
port,
|
|
263
263
|
token,
|
|
264
264
|
dataDirectory,
|
|
265
|
-
databasePath:
|
|
265
|
+
databasePath: join5(dataDirectory, "cohall.db"),
|
|
266
266
|
allowRemote
|
|
267
267
|
});
|
|
268
268
|
},
|
|
@@ -408,12 +408,12 @@ __export(exports_store, {
|
|
|
408
408
|
RelayStore: () => exports_store,
|
|
409
409
|
PersistenceError: () => PersistenceError
|
|
410
410
|
});
|
|
411
|
-
import { Context as Context2, Effect as Effect9, Layer as Layer2, Schema as
|
|
411
|
+
import { Context as Context2, Effect as Effect9, Layer as Layer2, Schema as Schema10 } from "effect";
|
|
412
412
|
import { createHash, randomBytes } from "node:crypto";
|
|
413
413
|
var PersistenceError, Service2, operationError = (operation) => (cause) => new PersistenceError({
|
|
414
414
|
operation,
|
|
415
415
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
416
|
-
}), decode = (operation, schema, value) =>
|
|
416
|
+
}), decode = (operation, schema, value) => Schema10.decodeUnknownEffect(schema)(value).pipe(Effect9.mapError(operationError(operation))), threadFromRow = (row) => decode("RelayStore.decodeThread", Thread, {
|
|
417
417
|
id: row.id,
|
|
418
418
|
title: row.title,
|
|
419
419
|
createdAt: row.created_at,
|
|
@@ -463,7 +463,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
463
463
|
kind: row.kind,
|
|
464
464
|
at: row.created_at,
|
|
465
465
|
...row.detail === null ? {} : { detail: row.detail }
|
|
466
|
-
}), rolesFromRow = (row) =>
|
|
466
|
+
}), rolesFromRow = (row) => Schema10.decodeUnknownSync(Schema10.Array(ConnectionRole))(JSON.parse(row.roles_json)), authSessionFromRow = (row) => {
|
|
467
467
|
const roles = rolesFromRow(row);
|
|
468
468
|
const role = roles[0];
|
|
469
469
|
return role === undefined ? Effect9.fail(new PersistenceError({
|
|
@@ -904,7 +904,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
|
|
|
904
904
|
if (pairing === null) {
|
|
905
905
|
throw new Error("Pairing credential is invalid, expired, or already used");
|
|
906
906
|
}
|
|
907
|
-
const roles =
|
|
907
|
+
const roles = Schema10.decodeUnknownSync(Schema10.Array(ConnectionRole))(JSON.parse(pairing.roles_json));
|
|
908
908
|
const deviceId = roles.includes("device") ? makeDeviceId() : undefined;
|
|
909
909
|
const consumed = db.query("UPDATE auth_pairings SET used_at = ? WHERE token_hash = ? AND used_at IS NULL").run(timestamp, pairing.token_hash);
|
|
910
910
|
if (consumed.changes !== 1) {
|
|
@@ -1075,7 +1075,7 @@ var init_store = __esm(() => {
|
|
|
1075
1075
|
init_src();
|
|
1076
1076
|
init_database();
|
|
1077
1077
|
init_store();
|
|
1078
|
-
PersistenceError = class PersistenceError extends
|
|
1078
|
+
PersistenceError = class PersistenceError extends Schema10.TaggedErrorClass()("RelayStore.PersistenceError", { operation: Schema10.String, message: Schema10.String }) {
|
|
1079
1079
|
};
|
|
1080
1080
|
Service2 = class Service2 extends Context2.Service()("@cohall/RelayStore") {
|
|
1081
1081
|
};
|
|
@@ -1086,9 +1086,9 @@ var exports_main = {};
|
|
|
1086
1086
|
__export(exports_main, {
|
|
1087
1087
|
runRelay: () => runRelay
|
|
1088
1088
|
});
|
|
1089
|
-
import { Effect as Effect10, ManagedRuntime, Schema as
|
|
1089
|
+
import { Effect as Effect10, ManagedRuntime, Schema as Schema11 } from "effect";
|
|
1090
1090
|
import { createHash as createHash2, timingSafeEqual } from "node:crypto";
|
|
1091
|
-
import { access as access4, chmod as
|
|
1091
|
+
import { access as access4, chmod as chmod5 } from "node:fs/promises";
|
|
1092
1092
|
import { createServer } from "node:http";
|
|
1093
1093
|
import { platform as platform5 } from "node:os";
|
|
1094
1094
|
import { WebSocketServer } from "ws";
|
|
@@ -1173,7 +1173,7 @@ Content-Length: 0\r
|
|
|
1173
1173
|
}, body = (request, decode2) => Effect10.tryPromise({
|
|
1174
1174
|
try: () => request.json(),
|
|
1175
1175
|
catch: () => new RequestError({ status: 400, message: "Expected a JSON request body" })
|
|
1176
|
-
}).pipe(Effect10.flatMap(decode2), Effect10.mapError((cause) => cause instanceof RequestError ? cause : new RequestError({ status: 400, message: String(cause).slice(0, 2048) }))), pathId = (schema, value) =>
|
|
1176
|
+
}).pipe(Effect10.flatMap(decode2), Effect10.mapError((cause) => cause instanceof RequestError ? cause : new RequestError({ status: 400, message: String(cause).slice(0, 2048) }))), pathId = (schema, value) => Schema11.decodeUnknownEffect(schema)(value).pipe(Effect10.mapError(() => new RequestError({ status: 400, message: `Invalid identifier: ${value}` }))), scoreDevice = (device, input, sourceDeviceId) => {
|
|
1177
1177
|
const prompt = `${input.prompt} ${input.context ?? ""}`.toLowerCase();
|
|
1178
1178
|
const needsXcode = /\b(xcode|ios|macos|swiftui|app store)\b/.test(prompt);
|
|
1179
1179
|
const needsBrowser = /\b(browser|chrome|youtube|twitter|logged[- ]?in|signed[- ]?in|x\.com)\b/.test(prompt);
|
|
@@ -1216,7 +1216,7 @@ Content-Length: 0\r
|
|
|
1216
1216
|
yield* store.recover();
|
|
1217
1217
|
}));
|
|
1218
1218
|
if (platform5() !== "win32" && await access4(configuration.databasePath).then(() => true).catch(() => false)) {
|
|
1219
|
-
await
|
|
1219
|
+
await chmod5(configuration.databasePath, 384);
|
|
1220
1220
|
}
|
|
1221
1221
|
const hub = new Hub;
|
|
1222
1222
|
let connections = 0;
|
|
@@ -1570,9 +1570,9 @@ var init_main = __esm(async () => {
|
|
|
1570
1570
|
init_config();
|
|
1571
1571
|
init_hub();
|
|
1572
1572
|
init_store();
|
|
1573
|
-
RequestError = class RequestError extends
|
|
1574
|
-
status:
|
|
1575
|
-
message:
|
|
1573
|
+
RequestError = class RequestError extends Schema11.TaggedErrorClass()("Relay.RequestError", {
|
|
1574
|
+
status: Schema11.Int,
|
|
1575
|
+
message: Schema11.String
|
|
1576
1576
|
}) {
|
|
1577
1577
|
};
|
|
1578
1578
|
if (false) {}
|
|
@@ -1738,10 +1738,10 @@ init_src();
|
|
|
1738
1738
|
init_src();
|
|
1739
1739
|
import { Effect as Effect2, Schema as Schema3 } from "effect";
|
|
1740
1740
|
import { accessSync, constants } from "node:fs";
|
|
1741
|
-
import {
|
|
1741
|
+
import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
1742
|
+
import { homedir, platform, tmpdir } from "node:os";
|
|
1742
1743
|
import { delimiter, extname, isAbsolute, join } from "node:path";
|
|
1743
1744
|
import { spawn } from "node:child_process";
|
|
1744
|
-
import { createInterface } from "node:readline";
|
|
1745
1745
|
|
|
1746
1746
|
class ProviderUnavailableError extends Schema3.TaggedErrorClass()("CohallProvider.Unavailable", { provider: Provider, message: Schema3.String }) {
|
|
1747
1747
|
}
|
|
@@ -1758,7 +1758,7 @@ var executables = {
|
|
|
1758
1758
|
opencode: "opencode"
|
|
1759
1759
|
};
|
|
1760
1760
|
var providerEnvironment = () => ({
|
|
1761
|
-
...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("COHALL_"))),
|
|
1761
|
+
...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("COHALL_") || name === "COHALL_CONFIG")),
|
|
1762
1762
|
PATH: executableDirectories().join(delimiter)
|
|
1763
1763
|
});
|
|
1764
1764
|
var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? Object.fromEntries(Object.entries(value)) : undefined;
|
|
@@ -1879,42 +1879,90 @@ var claudeCommand = (options) => [
|
|
|
1879
1879
|
...options.model === undefined ? [] : ["--model", options.model],
|
|
1880
1880
|
...options.sessionId === undefined ? [] : ["--resume", options.sessionId]
|
|
1881
1881
|
];
|
|
1882
|
-
var opencodeCommand = (options) => [
|
|
1882
|
+
var opencodeCommand = (options, promptPath) => [
|
|
1883
1883
|
"opencode",
|
|
1884
1884
|
"run",
|
|
1885
1885
|
"--format",
|
|
1886
1886
|
"json",
|
|
1887
1887
|
...options.model === undefined ? [] : ["--model", options.model],
|
|
1888
1888
|
...options.sessionId === undefined ? [] : ["--session", options.sessionId],
|
|
1889
|
-
|
|
1889
|
+
"--file",
|
|
1890
|
+
promptPath,
|
|
1891
|
+
"Complete the task described in the attached Cohall prompt file."
|
|
1890
1892
|
];
|
|
1891
|
-
var
|
|
1893
|
+
var prepareCommand = async (options) => {
|
|
1892
1894
|
switch (options.provider) {
|
|
1893
1895
|
case "codex":
|
|
1894
|
-
return
|
|
1896
|
+
return {
|
|
1897
|
+
command: codexCommand(options),
|
|
1898
|
+
input: options.prompt,
|
|
1899
|
+
cleanup: () => Promise.resolve()
|
|
1900
|
+
};
|
|
1895
1901
|
case "claude-code":
|
|
1896
|
-
return
|
|
1897
|
-
|
|
1898
|
-
|
|
1902
|
+
return {
|
|
1903
|
+
command: claudeCommand(options),
|
|
1904
|
+
input: options.prompt,
|
|
1905
|
+
cleanup: () => Promise.resolve()
|
|
1906
|
+
};
|
|
1907
|
+
case "opencode": {
|
|
1908
|
+
const directory = await mkdtemp(join(tmpdir(), "cohall-opencode-"));
|
|
1909
|
+
const promptPath = join(directory, "prompt.md");
|
|
1910
|
+
try {
|
|
1911
|
+
if (platform() !== "win32") {
|
|
1912
|
+
await chmod(directory, 448);
|
|
1913
|
+
}
|
|
1914
|
+
await writeFile(promptPath, options.prompt, { mode: 384 });
|
|
1915
|
+
return {
|
|
1916
|
+
command: opencodeCommand(options, promptPath),
|
|
1917
|
+
cleanup: () => rm(directory, { recursive: true, force: true })
|
|
1918
|
+
};
|
|
1919
|
+
} catch (cause) {
|
|
1920
|
+
await rm(directory, { recursive: true, force: true }).catch(() => {
|
|
1921
|
+
return;
|
|
1922
|
+
});
|
|
1923
|
+
throw cause;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1899
1926
|
}
|
|
1900
1927
|
};
|
|
1901
1928
|
var forEachJsonEvent = async (stdout, onEvent) => {
|
|
1902
|
-
const
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
if (Buffer.byteLength(line) > 1024 * 1024) {
|
|
1909
|
-
throw new Error("Provider event exceeded 1024 KiB");
|
|
1910
|
-
}
|
|
1911
|
-
const event = record(JSON.parse(line));
|
|
1912
|
-
if (event !== undefined) {
|
|
1913
|
-
onEvent(event);
|
|
1914
|
-
}
|
|
1929
|
+
const limit = 1024 * 1024;
|
|
1930
|
+
let chunks = [];
|
|
1931
|
+
let size = 0;
|
|
1932
|
+
const append = (chunk) => {
|
|
1933
|
+
if (size + chunk.byteLength > limit) {
|
|
1934
|
+
throw new Error("Provider event exceeded 1024 KiB");
|
|
1915
1935
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1936
|
+
if (chunk.byteLength > 0) {
|
|
1937
|
+
chunks.push(chunk);
|
|
1938
|
+
size += chunk.byteLength;
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
const emit = () => {
|
|
1942
|
+
const line = Buffer.concat(chunks, size).toString("utf8");
|
|
1943
|
+
chunks = [];
|
|
1944
|
+
size = 0;
|
|
1945
|
+
if (line.trim().length === 0) {
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
const event = record(JSON.parse(line));
|
|
1949
|
+
if (event !== undefined) {
|
|
1950
|
+
onEvent(event);
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
for await (const chunk of stdout) {
|
|
1954
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
1955
|
+
let start = 0;
|
|
1956
|
+
for (let newline = bytes.indexOf(10, start);newline !== -1; ) {
|
|
1957
|
+
append(bytes.subarray(start, newline));
|
|
1958
|
+
emit();
|
|
1959
|
+
start = newline + 1;
|
|
1960
|
+
newline = bytes.indexOf(10, start);
|
|
1961
|
+
}
|
|
1962
|
+
append(bytes.subarray(start));
|
|
1963
|
+
}
|
|
1964
|
+
if (size > 0) {
|
|
1965
|
+
emit();
|
|
1918
1966
|
}
|
|
1919
1967
|
};
|
|
1920
1968
|
var parseCodex = async (stdout, existingSession) => {
|
|
@@ -2002,14 +2050,15 @@ var parse = async (options, stdout, onFailure) => {
|
|
|
2002
2050
|
var byteLength = (value) => new TextEncoder().encode(value).byteLength;
|
|
2003
2051
|
var run = (options) => Effect2.tryPromise({
|
|
2004
2052
|
try: async (signal) => {
|
|
2005
|
-
const
|
|
2006
|
-
const executable = commandName === undefined ? undefined : findExecutable(commandName);
|
|
2053
|
+
const executable = findExecutable(executables[options.provider]);
|
|
2007
2054
|
if (executable === undefined) {
|
|
2008
2055
|
throw new ProviderUnavailableError({
|
|
2009
2056
|
provider: options.provider,
|
|
2010
2057
|
message: `${executables[options.provider]} is not available on this device`
|
|
2011
2058
|
});
|
|
2012
2059
|
}
|
|
2060
|
+
const prepared = await prepareCommand(options);
|
|
2061
|
+
const [, ...arguments_] = prepared.command;
|
|
2013
2062
|
const child = spawn(executable, arguments_, {
|
|
2014
2063
|
cwd: options.cwd,
|
|
2015
2064
|
env: {
|
|
@@ -2037,36 +2086,46 @@ var run = (options) => Effect2.tryPromise({
|
|
|
2037
2086
|
killTimer.unref();
|
|
2038
2087
|
};
|
|
2039
2088
|
signal.addEventListener("abort", terminate, { once: true });
|
|
2040
|
-
child.stdin.end(
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2089
|
+
child.stdin.end(prepared.input);
|
|
2090
|
+
try {
|
|
2091
|
+
const [result, stderr, exitCode] = await Promise.all([
|
|
2092
|
+
parse(options, child.stdout, terminate),
|
|
2093
|
+
captureText(child.stderr, 64 * 1024),
|
|
2094
|
+
exited
|
|
2095
|
+
]);
|
|
2096
|
+
if (exitCode !== 0) {
|
|
2097
|
+
throw new ProviderRunError({
|
|
2098
|
+
provider: options.provider,
|
|
2099
|
+
message: failureMessage(executables[options.provider], stderr, exitCode),
|
|
2100
|
+
exitCode
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
if (byteLength(result.result) > 131072) {
|
|
2104
|
+
throw new ProviderRunError({
|
|
2105
|
+
provider: options.provider,
|
|
2106
|
+
message: "Provider result exceeded 128 KiB"
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
if (result.sessionId !== undefined && byteLength(result.sessionId) > 4096) {
|
|
2110
|
+
throw new ProviderRunError({
|
|
2111
|
+
provider: options.provider,
|
|
2112
|
+
message: "Provider session ID exceeded 4 KiB"
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
return result;
|
|
2116
|
+
} finally {
|
|
2117
|
+
signal.removeEventListener("abort", terminate);
|
|
2118
|
+
if (child.exitCode === null) {
|
|
2119
|
+
terminate();
|
|
2120
|
+
await exited.catch(() => {
|
|
2121
|
+
return;
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
if (killTimer !== undefined) {
|
|
2125
|
+
clearTimeout(killTimer);
|
|
2126
|
+
}
|
|
2127
|
+
await prepared.cleanup();
|
|
2068
2128
|
}
|
|
2069
|
-
return result;
|
|
2070
2129
|
},
|
|
2071
2130
|
catch: (cause) => {
|
|
2072
2131
|
if (cause instanceof ProviderUnavailableError || cause instanceof ProviderRunError) {
|
|
@@ -2080,10 +2139,10 @@ var run = (options) => Effect2.tryPromise({
|
|
|
2080
2139
|
});
|
|
2081
2140
|
|
|
2082
2141
|
// apps/device/src/cli.ts
|
|
2083
|
-
import { Effect as Effect5, Schema as
|
|
2084
|
-
import { access as access2, mkdir as
|
|
2142
|
+
import { Effect as Effect5, Schema as Schema7 } from "effect";
|
|
2143
|
+
import { access as access2, mkdir as mkdir3, readFile as readFile3, stat, writeFile as writeFile4 } from "node:fs/promises";
|
|
2085
2144
|
import { homedir as homedir3 } from "node:os";
|
|
2086
|
-
import { dirname as
|
|
2145
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
2087
2146
|
|
|
2088
2147
|
// skills/cohall/SKILL.md
|
|
2089
2148
|
var SKILL_default = `---
|
|
@@ -2222,7 +2281,7 @@ Return the diagnostic failure to the user rather than claiming remote work ran.
|
|
|
2222
2281
|
// apps/device/src/config.ts
|
|
2223
2282
|
init_src();
|
|
2224
2283
|
import { Effect as Effect3, Schema as Schema4 } from "effect";
|
|
2225
|
-
import { access, chmod, mkdir, readFile, realpath, rename, writeFile } from "node:fs/promises";
|
|
2284
|
+
import { access, chmod as chmod2, mkdir, readFile, realpath, rename, writeFile as writeFile2 } from "node:fs/promises";
|
|
2226
2285
|
import { homedir as homedir2, hostname, platform as platform2 } from "node:os";
|
|
2227
2286
|
import { dirname, join as join2, resolve } from "node:path";
|
|
2228
2287
|
var WorkspaceList = Schema4.Array(Schema4.NonEmptyString).check(Schema4.isMaxLength(64));
|
|
@@ -2296,7 +2355,7 @@ var readStoredConfiguration = async () => {
|
|
|
2296
2355
|
return;
|
|
2297
2356
|
}
|
|
2298
2357
|
if (platform2() !== "win32") {
|
|
2299
|
-
await
|
|
2358
|
+
await chmod2(path, 384);
|
|
2300
2359
|
}
|
|
2301
2360
|
try {
|
|
2302
2361
|
return Schema4.decodeUnknownSync(StoredConfiguration)(JSON.parse(await readFile(path, "utf8")));
|
|
@@ -2310,24 +2369,35 @@ var writeStoredConfiguration = async (configuration) => {
|
|
|
2310
2369
|
const temporary = `${path}.${process.pid}.tmp`;
|
|
2311
2370
|
await mkdir(directory, { recursive: true, mode: 448 });
|
|
2312
2371
|
if (platform2() !== "win32") {
|
|
2313
|
-
await
|
|
2372
|
+
await chmod2(directory, 448);
|
|
2314
2373
|
}
|
|
2315
|
-
await
|
|
2374
|
+
await writeFile2(temporary, `${JSON.stringify(configuration, null, 2)}
|
|
2316
2375
|
`, { mode: 384 });
|
|
2317
2376
|
await rename(temporary, path);
|
|
2318
2377
|
if (platform2() !== "win32") {
|
|
2319
|
-
await
|
|
2378
|
+
await chmod2(path, 384);
|
|
2379
|
+
}
|
|
2380
|
+
};
|
|
2381
|
+
var credentialsForRelay = (configuration, relayUrl) => {
|
|
2382
|
+
if (configuration?.relayUrl !== normalizeRelayUrl(relayUrl)) {
|
|
2383
|
+
return {};
|
|
2320
2384
|
}
|
|
2385
|
+
return {
|
|
2386
|
+
...configuration.clientToken === undefined ? {} : { clientToken: configuration.clientToken },
|
|
2387
|
+
...configuration.deviceToken === undefined ? {} : { deviceToken: configuration.deviceToken }
|
|
2388
|
+
};
|
|
2321
2389
|
};
|
|
2322
2390
|
var makeStoredConfiguration = async (input) => {
|
|
2323
2391
|
const existing = await readStoredConfiguration();
|
|
2392
|
+
const relayUrl = normalizeRelayUrl(input.relayUrl);
|
|
2393
|
+
const retainedCredentials = credentialsForRelay(existing, relayUrl);
|
|
2324
2394
|
const canonicalWorkspaces = input.workspaces === undefined ? existing?.workspaces ?? [] : await Effect3.runPromise(parseWorkspaces("", JSON.stringify(input.workspaces)));
|
|
2325
|
-
const clientToken = input.clientToken ??
|
|
2326
|
-
const deviceToken = input.deviceToken ??
|
|
2395
|
+
const clientToken = input.clientToken ?? retainedCredentials.clientToken;
|
|
2396
|
+
const deviceToken = input.deviceToken ?? retainedCredentials.deviceToken;
|
|
2327
2397
|
const providers = input.providers === "auto" ? undefined : input.providers ?? existing?.providers;
|
|
2328
2398
|
return StoredConfiguration.make({
|
|
2329
2399
|
version: 1,
|
|
2330
|
-
relayUrl
|
|
2400
|
+
relayUrl,
|
|
2331
2401
|
deviceId: existing?.deviceId ?? makeDeviceId(),
|
|
2332
2402
|
deviceName: input.deviceName ?? existing?.deviceName ?? hostname(),
|
|
2333
2403
|
workspaces: canonicalWorkspaces,
|
|
@@ -2368,12 +2438,13 @@ var storedOrDefaults = async () => {
|
|
|
2368
2438
|
var loadClientConfiguration = Effect3.tryPromise({
|
|
2369
2439
|
try: async () => {
|
|
2370
2440
|
const stored = await storedOrDefaults();
|
|
2371
|
-
const
|
|
2441
|
+
const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl);
|
|
2442
|
+
const token = process.env.COHALL_CLIENT_TOKEN ?? credentialsForRelay(stored, relayUrl).clientToken;
|
|
2372
2443
|
if (token === undefined) {
|
|
2373
2444
|
throw new Error(`No client credential. Run cohall join with a pairing token on stdin or set COHALL_CLIENT_TOKEN.`);
|
|
2374
2445
|
}
|
|
2375
2446
|
return ClientConfiguration.make({
|
|
2376
|
-
relayUrl
|
|
2447
|
+
relayUrl,
|
|
2377
2448
|
token,
|
|
2378
2449
|
...process.env.COHALL_THREAD_ID === undefined ? {} : { mcpThreadId: process.env.COHALL_THREAD_ID }
|
|
2379
2450
|
});
|
|
@@ -2397,7 +2468,8 @@ var loadOwnerConfiguration = Effect3.tryPromise({
|
|
|
2397
2468
|
var loadDeviceConfiguration = Effect3.tryPromise({
|
|
2398
2469
|
try: async () => {
|
|
2399
2470
|
const stored = await storedOrDefaults();
|
|
2400
|
-
const
|
|
2471
|
+
const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl);
|
|
2472
|
+
const token = process.env.COHALL_DEVICE_TOKEN ?? credentialsForRelay(stored, relayUrl).deviceToken;
|
|
2401
2473
|
if (token === undefined) {
|
|
2402
2474
|
throw new Error(`No device credential. Run cohall join with a pairing token on stdin or set COHALL_DEVICE_TOKEN.`);
|
|
2403
2475
|
}
|
|
@@ -2409,7 +2481,7 @@ var loadDeviceConfiguration = Effect3.tryPromise({
|
|
|
2409
2481
|
const sandbox = environmentSandbox() ?? stored.sandbox;
|
|
2410
2482
|
const providers = environmentProviders(stored);
|
|
2411
2483
|
return DeviceConfiguration.make({
|
|
2412
|
-
relayUrl
|
|
2484
|
+
relayUrl,
|
|
2413
2485
|
token,
|
|
2414
2486
|
id: process.env.COHALL_DEVICE_ID === undefined ? stored.deviceId : DeviceId.make(process.env.COHALL_DEVICE_ID),
|
|
2415
2487
|
name: process.env.COHALL_DEVICE_NAME ?? stored.deviceName,
|
|
@@ -2517,6 +2589,322 @@ var threadContext = Effect4.fn("Cohall.threadContext")(function* (client, thread
|
|
|
2517
2589
|
return yield* client.threadContext(threadId);
|
|
2518
2590
|
});
|
|
2519
2591
|
|
|
2592
|
+
// apps/device/src/upgrade.ts
|
|
2593
|
+
import { Schema as Schema6 } from "effect";
|
|
2594
|
+
import { execFile } from "node:child_process";
|
|
2595
|
+
import { chmod as chmod3, mkdir as mkdir2, readFile as readFile2, realpath as realpath2, rename as rename2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
|
|
2596
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
2597
|
+
import { platform as operatingSystem } from "node:os";
|
|
2598
|
+
var packageName = "@akshar5/cohall";
|
|
2599
|
+
var semanticVersion = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
2600
|
+
var PackageManager = Schema6.Literals(["npm", "bun", "pnpm"]);
|
|
2601
|
+
var RestartReceipt = Schema6.Struct({
|
|
2602
|
+
version: Schema6.String,
|
|
2603
|
+
fromVersion: Schema6.String,
|
|
2604
|
+
packageManager: PackageManager,
|
|
2605
|
+
pendingServices: Schema6.Array(Schema6.String),
|
|
2606
|
+
restartedServices: Schema6.Array(Schema6.String)
|
|
2607
|
+
});
|
|
2608
|
+
var normalizePath = (path) => path.replaceAll("\\", "/");
|
|
2609
|
+
var normalizeUpgradeTarget = (target) => {
|
|
2610
|
+
if (target === undefined || target === "latest") {
|
|
2611
|
+
return "latest";
|
|
2612
|
+
}
|
|
2613
|
+
const normalized = target.startsWith("v") ? target.slice(1) : target;
|
|
2614
|
+
if (!semanticVersion.test(normalized)) {
|
|
2615
|
+
throw new Error("--to must be latest or an exact semantic version");
|
|
2616
|
+
}
|
|
2617
|
+
return normalized;
|
|
2618
|
+
};
|
|
2619
|
+
var packageInstallation = (canonicalEntrypoint, entrypoint = canonicalEntrypoint) => {
|
|
2620
|
+
const path = normalizePath(canonicalEntrypoint);
|
|
2621
|
+
const marker = `/node_modules/${packageName}/`;
|
|
2622
|
+
const packageIndex = path.lastIndexOf(marker);
|
|
2623
|
+
if (packageIndex === -1) {
|
|
2624
|
+
throw new Error("cohall upgrade requires a global npm, Bun, or pnpm installation; package-runner and source-checkout executions cannot upgrade themselves");
|
|
2625
|
+
}
|
|
2626
|
+
if (path.includes("/.npm/_npx/") || path.includes("/.bunx/") || path.includes("/pnpm/dlx/") || path.includes("/dlx/")) {
|
|
2627
|
+
throw new Error("This Cohall process is running from a temporary package-runner cache; install it globally before using cohall upgrade");
|
|
2628
|
+
}
|
|
2629
|
+
if (path.includes("/.bun/install/global/node_modules/")) {
|
|
2630
|
+
return { manager: "bun", entrypoint };
|
|
2631
|
+
}
|
|
2632
|
+
if (path.includes("/pnpm/global/") || path.includes("/.pnpm/")) {
|
|
2633
|
+
return { manager: "pnpm", entrypoint };
|
|
2634
|
+
}
|
|
2635
|
+
const nodeModulesParent = path.slice(0, packageIndex);
|
|
2636
|
+
const prefix = nodeModulesParent.endsWith("/lib") ? nodeModulesParent.slice(0, -"/lib".length) : nodeModulesParent;
|
|
2637
|
+
return { manager: "npm", prefix, entrypoint };
|
|
2638
|
+
};
|
|
2639
|
+
var packageInstallCommand = (installation, target) => {
|
|
2640
|
+
const specification = `${packageName}@${target}`;
|
|
2641
|
+
switch (installation.manager) {
|
|
2642
|
+
case "bun":
|
|
2643
|
+
return { command: "bun", arguments: ["add", "--global", specification] };
|
|
2644
|
+
case "pnpm":
|
|
2645
|
+
return { command: "pnpm", arguments: ["add", "--global", specification] };
|
|
2646
|
+
case "npm":
|
|
2647
|
+
return {
|
|
2648
|
+
command: "npm",
|
|
2649
|
+
arguments: [
|
|
2650
|
+
"install",
|
|
2651
|
+
"--global",
|
|
2652
|
+
...installation.prefix === undefined ? [] : ["--prefix", installation.prefix],
|
|
2653
|
+
specification
|
|
2654
|
+
]
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2658
|
+
var serviceCandidates = (runtimePlatform, uid) => {
|
|
2659
|
+
if (runtimePlatform === "linux") {
|
|
2660
|
+
const systemd = (manager, unit, device) => {
|
|
2661
|
+
const user = manager === "systemd-user" ? ["--user"] : [];
|
|
2662
|
+
return {
|
|
2663
|
+
id: `${manager}:${unit}`,
|
|
2664
|
+
label: unit,
|
|
2665
|
+
manager,
|
|
2666
|
+
device,
|
|
2667
|
+
check: { command: "systemctl", arguments: [...user, "is-active", "--quiet", unit] },
|
|
2668
|
+
restart: [{ command: "systemctl", arguments: [...user, "restart", unit] }]
|
|
2669
|
+
};
|
|
2670
|
+
};
|
|
2671
|
+
return [
|
|
2672
|
+
systemd("systemd-user", "cohall-relay.service", false),
|
|
2673
|
+
systemd("systemd-system", "cohall-relay.service", false),
|
|
2674
|
+
systemd("systemd-user", "cohall-device.service", true)
|
|
2675
|
+
];
|
|
2676
|
+
}
|
|
2677
|
+
if (runtimePlatform === "darwin") {
|
|
2678
|
+
const domain = `gui/${uid ?? 0}`;
|
|
2679
|
+
const launchd = (label, device) => ({
|
|
2680
|
+
id: `launchd:${label}`,
|
|
2681
|
+
label,
|
|
2682
|
+
manager: "launchd",
|
|
2683
|
+
device,
|
|
2684
|
+
check: { command: "launchctl", arguments: ["print", `${domain}/${label}`] },
|
|
2685
|
+
activeOutput: /\bstate\s*=\s*running\b/,
|
|
2686
|
+
restart: [{ command: "launchctl", arguments: ["kickstart", "-k", `${domain}/${label}`] }]
|
|
2687
|
+
});
|
|
2688
|
+
return [launchd("com.cohall.relay", false), launchd("com.cohall.device", true)];
|
|
2689
|
+
}
|
|
2690
|
+
if (runtimePlatform === "win32") {
|
|
2691
|
+
return [
|
|
2692
|
+
{
|
|
2693
|
+
id: "scheduled-task:Cohall Device",
|
|
2694
|
+
label: "Cohall Device",
|
|
2695
|
+
manager: "scheduled-task",
|
|
2696
|
+
device: true,
|
|
2697
|
+
check: {
|
|
2698
|
+
command: "powershell.exe",
|
|
2699
|
+
arguments: [
|
|
2700
|
+
"-NoProfile",
|
|
2701
|
+
"-NonInteractive",
|
|
2702
|
+
"-Command",
|
|
2703
|
+
"if ((Get-ScheduledTask -TaskName 'Cohall Device' -ErrorAction SilentlyContinue).State -eq 'Running') { exit 0 } else { exit 1 }"
|
|
2704
|
+
]
|
|
2705
|
+
},
|
|
2706
|
+
restart: [
|
|
2707
|
+
{
|
|
2708
|
+
command: "schtasks",
|
|
2709
|
+
arguments: ["/End", "/TN", "Cohall Device"],
|
|
2710
|
+
allowFailure: true
|
|
2711
|
+
},
|
|
2712
|
+
{ command: "schtasks", arguments: ["/Run", "/TN", "Cohall Device"] }
|
|
2713
|
+
]
|
|
2714
|
+
}
|
|
2715
|
+
];
|
|
2716
|
+
}
|
|
2717
|
+
return [];
|
|
2718
|
+
};
|
|
2719
|
+
var defaultRunner = {
|
|
2720
|
+
run: (command, arguments_, timeoutMs = 300000) => new Promise((resolve2) => {
|
|
2721
|
+
execFile(command, [...arguments_], { encoding: "utf8", maxBuffer: 1024 * 1024, timeout: timeoutMs, windowsHide: true }, (error, stdout, stderr) => {
|
|
2722
|
+
resolve2({
|
|
2723
|
+
exitCode: typeof error?.code === "number" ? error.code : error === null ? 0 : 1,
|
|
2724
|
+
stdout,
|
|
2725
|
+
stderr,
|
|
2726
|
+
...error === null ? {} : { error: error.message }
|
|
2727
|
+
});
|
|
2728
|
+
});
|
|
2729
|
+
})
|
|
2730
|
+
};
|
|
2731
|
+
var checked = async (runner, invocation, timeoutMs) => {
|
|
2732
|
+
const result = await runner.run(invocation.command, invocation.arguments, timeoutMs);
|
|
2733
|
+
if (result.exitCode === 0 || invocation.allowFailure === true) {
|
|
2734
|
+
return result;
|
|
2735
|
+
}
|
|
2736
|
+
const detail = (result.stderr.trim() || result.stdout.trim() || result.error || "unknown error").slice(0, 16384);
|
|
2737
|
+
throw new Error(`${invocation.command} ${invocation.arguments.join(" ")} failed with status ${result.exitCode}: ${detail}`);
|
|
2738
|
+
};
|
|
2739
|
+
var activeServices = async (runner, candidates) => {
|
|
2740
|
+
const active = [];
|
|
2741
|
+
for (const service of candidates) {
|
|
2742
|
+
const result = await runner.run(service.check.command, service.check.arguments, 1e4);
|
|
2743
|
+
if (result.exitCode === 0 && (service.activeOutput === undefined || service.activeOutput.test(`${result.stdout}
|
|
2744
|
+
${result.stderr}`))) {
|
|
2745
|
+
active.push(service);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
return active;
|
|
2749
|
+
};
|
|
2750
|
+
var readReceipt = async (path) => readFile2(path, "utf8").then((content) => Schema6.decodeUnknownOption(RestartReceipt)(JSON.parse(content))).then((decoded) => decoded._tag === "Some" ? decoded.value : undefined).catch(() => {
|
|
2751
|
+
return;
|
|
2752
|
+
});
|
|
2753
|
+
var writeReceipt = async (path, value) => {
|
|
2754
|
+
const directory = dirname2(path);
|
|
2755
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
2756
|
+
await mkdir2(directory, { recursive: true, mode: 448 });
|
|
2757
|
+
if (operatingSystem() !== "win32") {
|
|
2758
|
+
await chmod3(directory, 448);
|
|
2759
|
+
}
|
|
2760
|
+
await writeFile3(temporary, `${JSON.stringify(value, null, 2)}
|
|
2761
|
+
`, { mode: 384 });
|
|
2762
|
+
await rename2(temporary, path);
|
|
2763
|
+
};
|
|
2764
|
+
var installedVersion = async (entrypoint) => {
|
|
2765
|
+
const canonicalEntrypoint = await realpath2(entrypoint);
|
|
2766
|
+
const metadata = JSON.parse(await readFile2(join3(dirname2(dirname2(canonicalEntrypoint)), "package.json"), "utf8"));
|
|
2767
|
+
if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || metadata.name !== packageName || !("version" in metadata) || typeof metadata.version !== "string") {
|
|
2768
|
+
throw new Error(`Could not verify the installed ${packageName} version`);
|
|
2769
|
+
}
|
|
2770
|
+
return metadata.version;
|
|
2771
|
+
};
|
|
2772
|
+
var restartReceiptPath = () => join3(dirname2(configurationPath()), "upgrade-restart.json");
|
|
2773
|
+
var restartServices = async (runner, services, statePath, state) => {
|
|
2774
|
+
let current = state;
|
|
2775
|
+
for (const service of services) {
|
|
2776
|
+
for (const invocation of service.restart) {
|
|
2777
|
+
await checked(runner, invocation, 60000);
|
|
2778
|
+
}
|
|
2779
|
+
current = {
|
|
2780
|
+
...current,
|
|
2781
|
+
pendingServices: current.pendingServices.filter((id) => id !== service.id),
|
|
2782
|
+
restartedServices: [...current.restartedServices, service.id]
|
|
2783
|
+
};
|
|
2784
|
+
await writeReceipt(statePath, current);
|
|
2785
|
+
}
|
|
2786
|
+
return current;
|
|
2787
|
+
};
|
|
2788
|
+
var upgrade = async (options) => {
|
|
2789
|
+
const runner = options.runner ?? defaultRunner;
|
|
2790
|
+
const runtimePlatform = options.platform ?? process.platform;
|
|
2791
|
+
const uid = options.uid ?? process.getuid?.();
|
|
2792
|
+
const candidates = serviceCandidates(runtimePlatform, uid);
|
|
2793
|
+
const statePath = options.statePath ?? restartReceiptPath();
|
|
2794
|
+
const previous = await readReceipt(statePath);
|
|
2795
|
+
if (previous?.version === options.currentVersion) {
|
|
2796
|
+
const byId = new Map(candidates.map((service) => [service.id, service]));
|
|
2797
|
+
const pending = previous.pendingServices.flatMap((id) => {
|
|
2798
|
+
const service = byId.get(id);
|
|
2799
|
+
return service === undefined ? [] : [service];
|
|
2800
|
+
});
|
|
2801
|
+
const active = await activeServices(runner, pending);
|
|
2802
|
+
if (options.dryRun || !options.restart) {
|
|
2803
|
+
return {
|
|
2804
|
+
upgraded: previous.fromVersion !== previous.version,
|
|
2805
|
+
from_version: previous.fromVersion,
|
|
2806
|
+
installed_version: previous.version,
|
|
2807
|
+
requested_version: previous.version,
|
|
2808
|
+
package_manager: previous.packageManager,
|
|
2809
|
+
services_restarted: previous.restartedServices,
|
|
2810
|
+
services_pending_restart: active.map((service) => service.id),
|
|
2811
|
+
resumed_after_restart: false,
|
|
2812
|
+
dry_run: options.dryRun
|
|
2813
|
+
};
|
|
2814
|
+
}
|
|
2815
|
+
const activeIds = new Set(active.map((service) => service.id));
|
|
2816
|
+
const completedByRestart = options.delegated === true ? pending.filter((service) => service.device && activeIds.has(service.id)) : [];
|
|
2817
|
+
const completedIds = new Set(completedByRestart.map((service) => service.id));
|
|
2818
|
+
const remaining = active.filter((service) => !completedIds.has(service.id));
|
|
2819
|
+
let resumed = {
|
|
2820
|
+
...previous,
|
|
2821
|
+
pendingServices: previous.pendingServices.filter((id) => !completedIds.has(id)),
|
|
2822
|
+
restartedServices: [
|
|
2823
|
+
...previous.restartedServices,
|
|
2824
|
+
...completedByRestart.map((service) => service.id)
|
|
2825
|
+
]
|
|
2826
|
+
};
|
|
2827
|
+
if (remaining.length > 0) {
|
|
2828
|
+
await writeReceipt(statePath, resumed);
|
|
2829
|
+
resumed = await restartServices(runner, remaining, statePath, resumed);
|
|
2830
|
+
}
|
|
2831
|
+
await rm2(statePath, { force: true });
|
|
2832
|
+
return {
|
|
2833
|
+
upgraded: previous.fromVersion !== previous.version,
|
|
2834
|
+
from_version: previous.fromVersion,
|
|
2835
|
+
installed_version: previous.version,
|
|
2836
|
+
requested_version: previous.version,
|
|
2837
|
+
package_manager: previous.packageManager,
|
|
2838
|
+
services_restarted: resumed.restartedServices,
|
|
2839
|
+
services_pending_restart: [],
|
|
2840
|
+
resumed_after_restart: true,
|
|
2841
|
+
dry_run: false
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
await rm2(statePath, { force: true });
|
|
2845
|
+
const target = normalizeUpgradeTarget(options.target);
|
|
2846
|
+
const entrypoint = options.entrypoint ?? process.argv[1];
|
|
2847
|
+
if (entrypoint === undefined) {
|
|
2848
|
+
throw new Error("Could not resolve the Cohall executable path");
|
|
2849
|
+
}
|
|
2850
|
+
const installation = packageInstallation(await realpath2(entrypoint), entrypoint);
|
|
2851
|
+
const install = packageInstallCommand(installation, target);
|
|
2852
|
+
const services = await activeServices(runner, candidates);
|
|
2853
|
+
if (options.dryRun) {
|
|
2854
|
+
return {
|
|
2855
|
+
upgraded: false,
|
|
2856
|
+
from_version: options.currentVersion,
|
|
2857
|
+
installed_version: options.currentVersion,
|
|
2858
|
+
requested_version: target,
|
|
2859
|
+
package_manager: installation.manager,
|
|
2860
|
+
services_restarted: [],
|
|
2861
|
+
services_pending_restart: options.restart ? services.map((service) => service.id) : [],
|
|
2862
|
+
resumed_after_restart: false,
|
|
2863
|
+
dry_run: true
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
await checked(runner, install);
|
|
2867
|
+
const nextVersion = await installedVersion(entrypoint);
|
|
2868
|
+
if (target !== "latest" && nextVersion !== target) {
|
|
2869
|
+
throw new Error(`Installed Cohall ${nextVersion}, expected ${target}`);
|
|
2870
|
+
}
|
|
2871
|
+
const upgraded = nextVersion !== options.currentVersion;
|
|
2872
|
+
if (!options.restart || services.length === 0) {
|
|
2873
|
+
return {
|
|
2874
|
+
upgraded,
|
|
2875
|
+
from_version: options.currentVersion,
|
|
2876
|
+
installed_version: nextVersion,
|
|
2877
|
+
requested_version: target,
|
|
2878
|
+
package_manager: installation.manager,
|
|
2879
|
+
services_restarted: [],
|
|
2880
|
+
services_pending_restart: options.restart ? [] : services.map((service) => service.id),
|
|
2881
|
+
resumed_after_restart: false,
|
|
2882
|
+
dry_run: false
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
const initial = {
|
|
2886
|
+
version: nextVersion,
|
|
2887
|
+
fromVersion: options.currentVersion,
|
|
2888
|
+
packageManager: installation.manager,
|
|
2889
|
+
pendingServices: services.map((service) => service.id),
|
|
2890
|
+
restartedServices: []
|
|
2891
|
+
};
|
|
2892
|
+
await writeReceipt(statePath, initial);
|
|
2893
|
+
const completed = await restartServices(runner, services, statePath, initial);
|
|
2894
|
+
await rm2(statePath, { force: true });
|
|
2895
|
+
return {
|
|
2896
|
+
upgraded,
|
|
2897
|
+
from_version: options.currentVersion,
|
|
2898
|
+
installed_version: nextVersion,
|
|
2899
|
+
requested_version: target,
|
|
2900
|
+
package_manager: installation.manager,
|
|
2901
|
+
services_restarted: completed.restartedServices,
|
|
2902
|
+
services_pending_restart: [],
|
|
2903
|
+
resumed_after_restart: false,
|
|
2904
|
+
dry_run: false
|
|
2905
|
+
};
|
|
2906
|
+
};
|
|
2907
|
+
|
|
2520
2908
|
// apps/device/src/cli.ts
|
|
2521
2909
|
var valueOptions = new Set([
|
|
2522
2910
|
"context",
|
|
@@ -2534,9 +2922,10 @@ var valueOptions = new Set([
|
|
|
2534
2922
|
"thread",
|
|
2535
2923
|
"timeout",
|
|
2536
2924
|
"token-file",
|
|
2925
|
+
"to",
|
|
2537
2926
|
"workspace"
|
|
2538
2927
|
]);
|
|
2539
|
-
var flagOptions = new Set(["client-only", "follow", "no-wait"]);
|
|
2928
|
+
var flagOptions = new Set(["client-only", "dry-run", "follow", "no-restart", "no-wait"]);
|
|
2540
2929
|
var aliases = new Map([
|
|
2541
2930
|
["-c", "context"],
|
|
2542
2931
|
["-p", "prompt"],
|
|
@@ -2564,6 +2953,7 @@ Usage:
|
|
|
2564
2953
|
cohall sessions
|
|
2565
2954
|
cohall revoke <session-id>
|
|
2566
2955
|
cohall doctor
|
|
2956
|
+
cohall upgrade [--to version|latest] [--no-restart] [--dry-run]
|
|
2567
2957
|
cohall device
|
|
2568
2958
|
cohall relay
|
|
2569
2959
|
cohall mcp
|
|
@@ -2636,9 +3026,9 @@ var allowOptions = (arguments_, names) => {
|
|
|
2636
3026
|
}
|
|
2637
3027
|
}
|
|
2638
3028
|
};
|
|
2639
|
-
var noPositionals = (arguments_,
|
|
3029
|
+
var noPositionals = (arguments_, command) => {
|
|
2640
3030
|
if (arguments_.positionals.length > 0) {
|
|
2641
|
-
throw new Error(`${
|
|
3031
|
+
throw new Error(`${command} does not accept positional arguments`);
|
|
2642
3032
|
}
|
|
2643
3033
|
};
|
|
2644
3034
|
var identifier = (arguments_, label) => {
|
|
@@ -2666,7 +3056,7 @@ var readInput = async (arguments_, name) => {
|
|
|
2666
3056
|
if ((await stat(path)).size > 131072) {
|
|
2667
3057
|
throw new Error(`${name} file exceeds 128 KiB`);
|
|
2668
3058
|
}
|
|
2669
|
-
return
|
|
3059
|
+
return readFile3(path, "utf8");
|
|
2670
3060
|
}
|
|
2671
3061
|
const result = direct === "-" ? await readStdin(131072, name) : direct;
|
|
2672
3062
|
if (result !== undefined && result.length > 131072) {
|
|
@@ -2702,7 +3092,7 @@ var pairingToken = async (arguments_) => {
|
|
|
2702
3092
|
if (path !== undefined && (await stat(path)).size > 512) {
|
|
2703
3093
|
throw new Error("Pairing token file exceeds 512 bytes");
|
|
2704
3094
|
}
|
|
2705
|
-
const token = (path === undefined ? await readStdin(512, "Pairing token") : await
|
|
3095
|
+
const token = (path === undefined ? await readStdin(512, "Pairing token") : await readFile3(path, "utf8")).trim();
|
|
2706
3096
|
if (token.length === 0 || new TextEncoder().encode(token).byteLength > 256) {
|
|
2707
3097
|
throw new Error("Provide a pairing token on stdin or with --token-file");
|
|
2708
3098
|
}
|
|
@@ -2724,25 +3114,38 @@ var ownerClient = async () => {
|
|
|
2724
3114
|
};
|
|
2725
3115
|
var installSkill = async (target) => {
|
|
2726
3116
|
const roots = {
|
|
2727
|
-
agents:
|
|
2728
|
-
claude:
|
|
2729
|
-
opencode:
|
|
3117
|
+
agents: join4(homedir3(), ".agents", "skills", "cohall", "SKILL.md"),
|
|
3118
|
+
claude: join4(homedir3(), ".claude", "skills", "cohall", "SKILL.md"),
|
|
3119
|
+
opencode: join4(homedir3(), ".config", "opencode", "skills", "cohall", "SKILL.md")
|
|
2730
3120
|
};
|
|
2731
3121
|
const selected = target === "all" ? Object.values(roots) : (target in roots) ? [roots[target]] : undefined;
|
|
2732
3122
|
if (selected === undefined) {
|
|
2733
3123
|
throw new Error("Skill target must be agents, claude, opencode, or all");
|
|
2734
3124
|
}
|
|
2735
3125
|
for (const path of selected) {
|
|
2736
|
-
await
|
|
2737
|
-
await
|
|
3126
|
+
await mkdir3(dirname3(path), { recursive: true });
|
|
3127
|
+
await writeFile4(path, SKILL_default);
|
|
2738
3128
|
}
|
|
2739
3129
|
return selected;
|
|
2740
3130
|
};
|
|
2741
3131
|
var printHelp = () => console.log(help);
|
|
2742
3132
|
var printSkill = () => console.log(SKILL_default.trimEnd());
|
|
2743
|
-
var runCli = async (
|
|
3133
|
+
var runCli = async (command, raw) => {
|
|
2744
3134
|
const arguments_ = parseArguments(raw);
|
|
2745
|
-
if (
|
|
3135
|
+
if (command === "upgrade") {
|
|
3136
|
+
allowOptions(arguments_, ["dry-run", "no-restart", "to"]);
|
|
3137
|
+
noPositionals(arguments_, command);
|
|
3138
|
+
const target = option(arguments_, "to");
|
|
3139
|
+
print(await upgrade({
|
|
3140
|
+
currentVersion: version,
|
|
3141
|
+
...target === undefined ? {} : { target },
|
|
3142
|
+
restart: !arguments_.options.has("no-restart"),
|
|
3143
|
+
dryRun: arguments_.options.has("dry-run"),
|
|
3144
|
+
delegated: process.env.COHALL_PROVIDER !== undefined
|
|
3145
|
+
}));
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
if (command === "join") {
|
|
2746
3149
|
allowOptions(arguments_, [
|
|
2747
3150
|
"client-only",
|
|
2748
3151
|
"name",
|
|
@@ -2751,7 +3154,7 @@ var runCli = async (command2, raw) => {
|
|
|
2751
3154
|
"token-file",
|
|
2752
3155
|
"workspace"
|
|
2753
3156
|
]);
|
|
2754
|
-
noPositionals(arguments_,
|
|
3157
|
+
noPositionals(arguments_, command);
|
|
2755
3158
|
const token = await pairingToken(arguments_);
|
|
2756
3159
|
const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? "http://127.0.0.1:8787");
|
|
2757
3160
|
const existing = await readStoredConfiguration();
|
|
@@ -2793,9 +3196,9 @@ var runCli = async (command2, raw) => {
|
|
|
2793
3196
|
});
|
|
2794
3197
|
return;
|
|
2795
3198
|
}
|
|
2796
|
-
if (
|
|
3199
|
+
if (command === "config") {
|
|
2797
3200
|
allowOptions(arguments_, []);
|
|
2798
|
-
noPositionals(arguments_,
|
|
3201
|
+
noPositionals(arguments_, command);
|
|
2799
3202
|
const configuration2 = await readStoredConfiguration();
|
|
2800
3203
|
print({
|
|
2801
3204
|
config_path: configurationPath(),
|
|
@@ -2814,9 +3217,9 @@ var runCli = async (command2, raw) => {
|
|
|
2814
3217
|
});
|
|
2815
3218
|
return;
|
|
2816
3219
|
}
|
|
2817
|
-
if (
|
|
3220
|
+
if (command === "configure") {
|
|
2818
3221
|
allowOptions(arguments_, ["model", "name", "providers", "relay", "sandbox", "workspace"]);
|
|
2819
|
-
noPositionals(arguments_,
|
|
3222
|
+
noPositionals(arguments_, command);
|
|
2820
3223
|
const existing = await readStoredConfiguration();
|
|
2821
3224
|
const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? existing?.relayUrl ?? "http://127.0.0.1:8787");
|
|
2822
3225
|
const supplied = values(arguments_, "workspace");
|
|
@@ -2831,19 +3234,18 @@ var runCli = async (command2, raw) => {
|
|
|
2831
3234
|
deviceId: existing?.deviceId ?? makeDeviceId(),
|
|
2832
3235
|
deviceName: option(arguments_, "name") ?? existing?.deviceName ?? "cohall-device",
|
|
2833
3236
|
workspaces,
|
|
2834
|
-
...existing
|
|
2835
|
-
...existing?.deviceToken === undefined ? {} : { deviceToken: existing.deviceToken },
|
|
3237
|
+
...credentialsForRelay(existing, relayUrl),
|
|
2836
3238
|
...providers === undefined ? {} : { providers },
|
|
2837
3239
|
...model === undefined ? {} : { model },
|
|
2838
3240
|
...(sandbox ?? existing?.sandbox) === undefined ? {} : {
|
|
2839
|
-
sandbox:
|
|
3241
|
+
sandbox: Schema7.decodeUnknownSync(Schema7.Literals(["read-only", "workspace-write", "danger-full-access"]))(sandbox ?? existing?.sandbox)
|
|
2840
3242
|
}
|
|
2841
3243
|
});
|
|
2842
3244
|
await writeStoredConfiguration(configuration2);
|
|
2843
3245
|
print({ configured: true, config_path: configurationPath() });
|
|
2844
3246
|
return;
|
|
2845
3247
|
}
|
|
2846
|
-
if (
|
|
3248
|
+
if (command === "skill") {
|
|
2847
3249
|
allowOptions(arguments_, []);
|
|
2848
3250
|
if (arguments_.positionals.length === 0) {
|
|
2849
3251
|
printSkill();
|
|
@@ -2855,9 +3257,9 @@ var runCli = async (command2, raw) => {
|
|
|
2855
3257
|
print({ installed: await installSkill(arguments_.positionals[1] ?? "all") });
|
|
2856
3258
|
return;
|
|
2857
3259
|
}
|
|
2858
|
-
if (
|
|
3260
|
+
if (command === "integrations") {
|
|
2859
3261
|
allowOptions(arguments_, []);
|
|
2860
|
-
noPositionals(arguments_,
|
|
3262
|
+
noPositionals(arguments_, command);
|
|
2861
3263
|
print({
|
|
2862
3264
|
cli_skill: "npx -y @akshar5/cohall skill install all",
|
|
2863
3265
|
codex_mcp: "codex mcp add cohall -- npx -y @akshar5/cohall mcp",
|
|
@@ -2874,11 +3276,12 @@ var runCli = async (command2, raw) => {
|
|
|
2874
3276
|
});
|
|
2875
3277
|
return;
|
|
2876
3278
|
}
|
|
2877
|
-
if (
|
|
3279
|
+
if (command === "doctor") {
|
|
2878
3280
|
allowOptions(arguments_, []);
|
|
2879
|
-
noPositionals(arguments_,
|
|
3281
|
+
noPositionals(arguments_, command);
|
|
2880
3282
|
const configuration2 = await readStoredConfiguration();
|
|
2881
3283
|
const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? configuration2?.relayUrl ?? "http://127.0.0.1:8787");
|
|
3284
|
+
const storedCredentials = credentialsForRelay(configuration2, relayUrl);
|
|
2882
3285
|
const response = await fetch(`${relayUrl}/api/health`, {
|
|
2883
3286
|
signal: AbortSignal.timeout(1e4)
|
|
2884
3287
|
}).catch(() => {
|
|
@@ -2890,8 +3293,8 @@ var runCli = async (command2, raw) => {
|
|
|
2890
3293
|
provider,
|
|
2891
3294
|
findExecutable(provider === "claude-code" ? "claude" : provider) ?? "not found"
|
|
2892
3295
|
]));
|
|
2893
|
-
const clientToken = process.env.COHALL_CLIENT_TOKEN ??
|
|
2894
|
-
const hasDeviceCredential = process.env.COHALL_DEVICE_TOKEN !== undefined ||
|
|
3296
|
+
const clientToken = process.env.COHALL_CLIENT_TOKEN ?? storedCredentials.clientToken;
|
|
3297
|
+
const hasDeviceCredential = process.env.COHALL_DEVICE_TOKEN !== undefined || storedCredentials.deviceToken !== undefined;
|
|
2895
3298
|
const deviceId = process.env.COHALL_DEVICE_ID ?? configuration2?.deviceId;
|
|
2896
3299
|
const currentDevice = response?.ok !== true || clientToken === undefined || deviceId === undefined ? undefined : await Effect5.runPromise(exports_src.make({ baseUrl: relayUrl, token: clientToken }).devices()).then((devices) => devices.find((device) => device.id === deviceId)).catch(() => {
|
|
2897
3300
|
return;
|
|
@@ -2908,7 +3311,7 @@ var runCli = async (command2, raw) => {
|
|
|
2908
3311
|
relay: response?.ok === true ? "reachable" : "unreachable",
|
|
2909
3312
|
relay_url: relayUrl,
|
|
2910
3313
|
config_path: configurationPath(),
|
|
2911
|
-
client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined ||
|
|
3314
|
+
client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || storedCredentials.clientToken !== undefined,
|
|
2912
3315
|
device_credential: hasDeviceCredential,
|
|
2913
3316
|
workspaces: configuration2?.workspaces ?? [],
|
|
2914
3317
|
providers: providerExecutables,
|
|
@@ -2920,11 +3323,11 @@ var runCli = async (command2, raw) => {
|
|
|
2920
3323
|
});
|
|
2921
3324
|
return;
|
|
2922
3325
|
}
|
|
2923
|
-
if (
|
|
3326
|
+
if (command === "pair" || command === "sessions" || command === "revoke") {
|
|
2924
3327
|
const { configuration: configuration2, relay: relay2 } = await ownerClient();
|
|
2925
|
-
if (
|
|
3328
|
+
if (command === "pair") {
|
|
2926
3329
|
allowOptions(arguments_, ["client-only", "label"]);
|
|
2927
|
-
noPositionals(arguments_,
|
|
3330
|
+
noPositionals(arguments_, command);
|
|
2928
3331
|
const roles = arguments_.options.has("client-only") ? ["client"] : ["client", "device"];
|
|
2929
3332
|
const credential = await Effect5.runPromise(relay2.createPairing({ label: option(arguments_, "label") ?? "Cohall device", roles }));
|
|
2930
3333
|
print({
|
|
@@ -2935,24 +3338,24 @@ var runCli = async (command2, raw) => {
|
|
|
2935
3338
|
});
|
|
2936
3339
|
return;
|
|
2937
3340
|
}
|
|
2938
|
-
if (
|
|
3341
|
+
if (command === "sessions") {
|
|
2939
3342
|
allowOptions(arguments_, []);
|
|
2940
|
-
noPositionals(arguments_,
|
|
3343
|
+
noPositionals(arguments_, command);
|
|
2941
3344
|
print(await Effect5.runPromise(relay2.authSessions()));
|
|
2942
3345
|
return;
|
|
2943
3346
|
}
|
|
2944
3347
|
allowOptions(arguments_, []);
|
|
2945
|
-
print(await Effect5.runPromise(relay2.revokeAuthSession(
|
|
3348
|
+
print(await Effect5.runPromise(relay2.revokeAuthSession(Schema7.decodeUnknownSync(AuthSessionId)(identifier(arguments_, "session id")))));
|
|
2946
3349
|
return;
|
|
2947
3350
|
}
|
|
2948
3351
|
const { configuration, relay } = await client();
|
|
2949
|
-
if (
|
|
3352
|
+
if (command === "devices") {
|
|
2950
3353
|
allowOptions(arguments_, []);
|
|
2951
|
-
noPositionals(arguments_,
|
|
3354
|
+
noPositionals(arguments_, command);
|
|
2952
3355
|
print(await Effect5.runPromise(relay.devices()));
|
|
2953
3356
|
return;
|
|
2954
3357
|
}
|
|
2955
|
-
if (
|
|
3358
|
+
if (command === "delegate") {
|
|
2956
3359
|
allowOptions(arguments_, [
|
|
2957
3360
|
"context",
|
|
2958
3361
|
"context-file",
|
|
@@ -2985,22 +3388,22 @@ var runCli = async (command2, raw) => {
|
|
|
2985
3388
|
prompt,
|
|
2986
3389
|
...target === undefined ? {} : { target },
|
|
2987
3390
|
...context === undefined ? {} : { context },
|
|
2988
|
-
...thread === undefined ? {} : { threadId:
|
|
3391
|
+
...thread === undefined ? {} : { threadId: Schema7.decodeUnknownSync(ThreadId)(thread) },
|
|
2989
3392
|
...workspace === undefined ? {} : { workspace },
|
|
2990
|
-
...provider === undefined ? {} : { provider:
|
|
3393
|
+
...provider === undefined ? {} : { provider: Schema7.decodeUnknownSync(Provider)(provider) }
|
|
2991
3394
|
}));
|
|
2992
3395
|
print(taskResult(arguments_.options.has("no-wait") ? task : await Effect5.runPromise(waitForTask(relay, task, timeout(arguments_)))));
|
|
2993
3396
|
return;
|
|
2994
3397
|
}
|
|
2995
|
-
if (
|
|
3398
|
+
if (command === "status") {
|
|
2996
3399
|
allowOptions(arguments_, []);
|
|
2997
|
-
const id =
|
|
3400
|
+
const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
|
|
2998
3401
|
print(taskResult(await Effect5.runPromise(relay.getTask(id))));
|
|
2999
3402
|
return;
|
|
3000
3403
|
}
|
|
3001
|
-
if (
|
|
3404
|
+
if (command === "trace") {
|
|
3002
3405
|
allowOptions(arguments_, ["follow"]);
|
|
3003
|
-
const id =
|
|
3406
|
+
const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
|
|
3004
3407
|
if (arguments_.options.has("follow")) {
|
|
3005
3408
|
await Effect5.runPromise(followTaskTrace(relay, id, printLine));
|
|
3006
3409
|
return;
|
|
@@ -3008,37 +3411,39 @@ var runCli = async (command2, raw) => {
|
|
|
3008
3411
|
print(await Effect5.runPromise(relay.traceTask(id)));
|
|
3009
3412
|
return;
|
|
3010
3413
|
}
|
|
3011
|
-
if (
|
|
3414
|
+
if (command === "wait") {
|
|
3012
3415
|
allowOptions(arguments_, ["timeout"]);
|
|
3013
|
-
const id =
|
|
3416
|
+
const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
|
|
3014
3417
|
const task = await Effect5.runPromise(relay.getTask(id));
|
|
3015
3418
|
print(taskResult(await Effect5.runPromise(waitForTask(relay, task, timeout(arguments_)))));
|
|
3016
3419
|
return;
|
|
3017
3420
|
}
|
|
3018
|
-
if (
|
|
3421
|
+
if (command === "cancel") {
|
|
3019
3422
|
allowOptions(arguments_, []);
|
|
3020
|
-
const id =
|
|
3423
|
+
const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
|
|
3021
3424
|
print(taskResult(await Effect5.runPromise(relay.cancelTask(id))));
|
|
3022
3425
|
return;
|
|
3023
3426
|
}
|
|
3024
|
-
if (
|
|
3427
|
+
if (command === "thread") {
|
|
3025
3428
|
allowOptions(arguments_, []);
|
|
3026
|
-
const id =
|
|
3429
|
+
const id = Schema7.decodeUnknownSync(ThreadId)(identifier(arguments_, "thread id"));
|
|
3027
3430
|
print(await Effect5.runPromise(threadContext(relay, id)));
|
|
3028
3431
|
return;
|
|
3029
3432
|
}
|
|
3030
|
-
throw new Error(`Unknown Cohall command: ${
|
|
3433
|
+
throw new Error(`Unknown Cohall command: ${command}`);
|
|
3031
3434
|
};
|
|
3032
3435
|
|
|
3033
3436
|
// apps/device/src/daemon.ts
|
|
3034
3437
|
init_src();
|
|
3035
|
-
import { Effect as Effect6, Schedule as Schedule2, Schema as
|
|
3036
|
-
import { realpath as
|
|
3438
|
+
import { Effect as Effect6, Schedule as Schedule2, Schema as Schema8 } from "effect";
|
|
3439
|
+
import { realpath as realpath3 } from "node:fs/promises";
|
|
3037
3440
|
import { arch, hostname as hostname2, platform as platform3 } from "node:os";
|
|
3038
3441
|
import { basename, isAbsolute as isAbsolute2, relative } from "node:path";
|
|
3039
3442
|
import { WebSocket } from "ws";
|
|
3443
|
+
var maxRelayPayloadBytes = 256 * 1024;
|
|
3444
|
+
var maxQueuedRelayMessages = 8;
|
|
3040
3445
|
|
|
3041
|
-
class DeviceConnectionError extends
|
|
3446
|
+
class DeviceConnectionError extends Schema8.TaggedErrorClass()("Device.ConnectionError", { message: Schema8.String }) {
|
|
3042
3447
|
}
|
|
3043
3448
|
var socketUrl = (configuration) => {
|
|
3044
3449
|
const url = new URL(configuration.relayUrl);
|
|
@@ -3075,8 +3480,8 @@ var capabilities = (providers) => {
|
|
|
3075
3480
|
};
|
|
3076
3481
|
var selectProviders = (installed, configured) => configured === undefined ? installed : configured.filter((provider) => installed.includes(provider));
|
|
3077
3482
|
var describeDevice = (configuration, status = "online") => {
|
|
3078
|
-
const
|
|
3079
|
-
const platformName =
|
|
3483
|
+
const operatingSystem2 = platform3();
|
|
3484
|
+
const platformName = operatingSystem2 === "darwin" || operatingSystem2 === "linux" ? operatingSystem2 : operatingSystem2 === "win32" ? "windows" : "unknown";
|
|
3080
3485
|
const installed = availableProviders();
|
|
3081
3486
|
const providers = selectProviders(installed, configuration.providers);
|
|
3082
3487
|
return Device.make({
|
|
@@ -3101,7 +3506,7 @@ var allowedWorkspace = async (configuration, requested) => {
|
|
|
3101
3506
|
if (selected === undefined) {
|
|
3102
3507
|
throw new Error("No workspace is configured on this device");
|
|
3103
3508
|
}
|
|
3104
|
-
const candidate = await
|
|
3509
|
+
const candidate = await realpath3(selected);
|
|
3105
3510
|
const allowed = configuration.workspaces.some((root) => {
|
|
3106
3511
|
const child = relative(root, candidate);
|
|
3107
3512
|
return child === "" || !child.startsWith("..") && !isAbsolute2(child);
|
|
@@ -3235,7 +3640,10 @@ var cancel = (state, taskId) => {
|
|
|
3235
3640
|
};
|
|
3236
3641
|
var connect = (configuration, state) => Effect6.tryPromise({
|
|
3237
3642
|
try: (signal) => new Promise((complete) => {
|
|
3238
|
-
const socket = new WebSocket(socketUrl(configuration)
|
|
3643
|
+
const socket = new WebSocket(socketUrl(configuration), {
|
|
3644
|
+
maxPayload: maxRelayPayloadBytes,
|
|
3645
|
+
perMessageDeflate: false
|
|
3646
|
+
});
|
|
3239
3647
|
const heartbeat = setInterval(() => {
|
|
3240
3648
|
send(state, SocketEvent.make({
|
|
3241
3649
|
_tag: "DeviceHeartbeat",
|
|
@@ -3244,6 +3652,7 @@ var connect = (configuration, state) => Effect6.tryPromise({
|
|
|
3244
3652
|
}));
|
|
3245
3653
|
}, 15000);
|
|
3246
3654
|
let closed = false;
|
|
3655
|
+
let queuedMessages = 0;
|
|
3247
3656
|
const close = () => {
|
|
3248
3657
|
if (closed) {
|
|
3249
3658
|
return;
|
|
@@ -3263,8 +3672,16 @@ var connect = (configuration, state) => Effect6.tryPromise({
|
|
|
3263
3672
|
socket.send(JSON.stringify(SocketEvent.make({ _tag: "Authenticate", token: configuration.token })));
|
|
3264
3673
|
});
|
|
3265
3674
|
socket.on("message", (message) => {
|
|
3266
|
-
|
|
3675
|
+
if (queuedMessages >= maxQueuedRelayMessages) {
|
|
3676
|
+
socket.close(4008, "Message queue limit reached");
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
queuedMessages += 1;
|
|
3267
3680
|
state.processing = state.processing.then(async () => {
|
|
3681
|
+
if (closed) {
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
const text2 = Buffer.isBuffer(message) ? message.toString("utf8") : message instanceof ArrayBuffer ? Buffer.from(message).toString("utf8") : Array.isArray(message) ? Buffer.concat(message).toString("utf8") : Buffer.from(message).toString("utf8");
|
|
3268
3685
|
const event = await Effect6.runPromise(Effect6.try({
|
|
3269
3686
|
try: () => JSON.parse(text2),
|
|
3270
3687
|
catch: () => new DeviceConnectionError({ message: "Relay sent invalid JSON" })
|
|
@@ -3297,6 +3714,8 @@ var connect = (configuration, state) => Effect6.tryPromise({
|
|
|
3297
3714
|
}
|
|
3298
3715
|
}).catch((cause) => {
|
|
3299
3716
|
console.error(`Invalid relay event: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
3717
|
+
}).finally(() => {
|
|
3718
|
+
queuedMessages = Math.max(0, queuedMessages - 1);
|
|
3300
3719
|
});
|
|
3301
3720
|
});
|
|
3302
3721
|
socket.once("close", close);
|
|
@@ -3404,43 +3823,43 @@ var runMcp = async (configuration) => {
|
|
|
3404
3823
|
|
|
3405
3824
|
// apps/device/src/main.ts
|
|
3406
3825
|
var main = async () => {
|
|
3407
|
-
const
|
|
3408
|
-
if (
|
|
3826
|
+
const command = process.argv[2];
|
|
3827
|
+
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
3409
3828
|
printHelp();
|
|
3410
3829
|
return;
|
|
3411
3830
|
}
|
|
3412
|
-
if (
|
|
3831
|
+
if (command === "--version" || command === "version") {
|
|
3413
3832
|
console.log(version);
|
|
3414
3833
|
return;
|
|
3415
3834
|
}
|
|
3416
|
-
if (["device", "relay", "mcp"].includes(
|
|
3835
|
+
if (["device", "relay", "mcp"].includes(command) && process.argv.length > 3) {
|
|
3417
3836
|
const arguments_ = process.argv.slice(3);
|
|
3418
3837
|
if (arguments_.length === 1 && ["--help", "-h", "help"].includes(arguments_[0] ?? "")) {
|
|
3419
3838
|
printHelp();
|
|
3420
3839
|
return;
|
|
3421
3840
|
}
|
|
3422
|
-
throw new Error(`${
|
|
3841
|
+
throw new Error(`${command} does not accept arguments`);
|
|
3423
3842
|
}
|
|
3424
|
-
if (
|
|
3843
|
+
if (command === "relay") {
|
|
3425
3844
|
const { runRelay: runRelay2 } = await init_main().then(() => exports_main);
|
|
3426
3845
|
await runRelay2();
|
|
3427
3846
|
return;
|
|
3428
3847
|
}
|
|
3429
|
-
if (
|
|
3848
|
+
if (command === "device") {
|
|
3430
3849
|
const configuration = await Effect11.runPromise(loadDeviceConfiguration);
|
|
3431
3850
|
await Effect11.runPromise(runDaemon(configuration));
|
|
3432
3851
|
return;
|
|
3433
3852
|
}
|
|
3434
|
-
if (
|
|
3853
|
+
if (command === "mcp") {
|
|
3435
3854
|
const configuration = await Effect11.runPromise(loadClientConfiguration);
|
|
3436
3855
|
await runMcp(configuration);
|
|
3437
3856
|
return;
|
|
3438
3857
|
}
|
|
3439
|
-
await runCli(
|
|
3858
|
+
await runCli(command, process.argv.slice(3));
|
|
3440
3859
|
};
|
|
3441
3860
|
await main().catch((cause) => {
|
|
3442
3861
|
console.error(JSON.stringify({ error: cause instanceof Error ? cause.message : String(cause) }, null, 2));
|
|
3443
3862
|
process.exitCode = 1;
|
|
3444
3863
|
});
|
|
3445
3864
|
|
|
3446
|
-
//# debugId=
|
|
3865
|
+
//# debugId=ADB2C1D9ECE4F8B464756E2164756E21
|