@akshar5/cohall 0.6.0 → 0.6.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 +8 -0
- package/README.md +5 -0
- package/bin/cohall.js +73 -56
- package/bin/cohall.js.map +4 -4
- package/docs/install.md +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.1](https://github.com/AksharP5/cohall/compare/v0.6.0...v0.6.1) (2026-09-23)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **bot:** release rejected tasks without waiting six hours ([#67](https://github.com/AksharP5/cohall/issues/67)) ([e0d7a06](https://github.com/AksharP5/cohall/commit/e0d7a06545095c1fc52473b90cbff962dff2fd67))
|
|
9
|
+
* **upgrade:** preserve recovery state and resolve trusted tools ([#66](https://github.com/AksharP5/cohall/issues/66)) ([bccfb18](https://github.com/AksharP5/cohall/commit/bccfb18ccc278fdbc1caede52bdeb70f56d16d81))
|
|
10
|
+
|
|
3
11
|
## [0.6.0](https://github.com/AksharP5/cohall/compare/v0.5.6...v0.6.0) (2026-09-23)
|
|
4
12
|
|
|
5
13
|
|
package/README.md
CHANGED
|
@@ -248,6 +248,11 @@ the Grok Bot chat alone does not complete it. If no callback arrives within six
|
|
|
248
248
|
hours of the first dispatch, Cohall reports failure. That deadline does not
|
|
249
249
|
stop the Bot's ongoing work.
|
|
250
250
|
|
|
251
|
+
Pending or uncertain prompt acceptance is checked until the gateway confirms
|
|
252
|
+
acceptance or rejection, including after a worker restart. A rejection fails
|
|
253
|
+
the task promptly; gateway outages still allow the local callback to complete
|
|
254
|
+
it. Cohall never resends an uncertain dispatch.
|
|
255
|
+
|
|
251
256
|
Bots can delegate through the installed Cohall CLI, including to Codex on the
|
|
252
257
|
same computer. Link child work to the parent task:
|
|
253
258
|
|
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.6.
|
|
20
|
+
var version = "0.6.1", maxSocketPayloadBytes, bounded = (maxLength) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maxLength)), optionalText = (maxLength) => Schema.String.check(Schema.isMaxLength(maxLength)), boundedArray = (schema, maxLength) => Schema.Array(schema).check(Schema.isMaxLength(maxLength)), count, uuid = (name) => Schema.String.check(Schema.isUUID(4)).pipe(Schema.brand(name)), isoTimestamp = (name) => Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/, {
|
|
21
21
|
expected: "an ISO-8601 UTC timestamp"
|
|
22
22
|
})).pipe(Schema.brand(name)), BotId, Bot, DeviceId, ThreadId, MessageId, TaskId, AuthSessionId, OperationId, Timestamp, Platform, Provider, DeviceStatus, TaskStatus, OperationStatus, TaskTraceEventKind, MessageRole, ConnectionRole, AuthSession, CreatePairingInput, PairingCredential, ExchangePairingInput, SessionCredential, PairingResult, Capability, Workspace, Device, minimumDeviceOperationVersion = "0.5.0", parsedVersion = (value) => {
|
|
23
23
|
const match = /^v?(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
|
|
@@ -3785,15 +3785,10 @@ var discoverGrokBots = async (path) => {
|
|
|
3785
3785
|
...description === undefined ? {} : { description: description.slice(0, 512) }
|
|
3786
3786
|
}));
|
|
3787
3787
|
};
|
|
3788
|
-
var poll =
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
})), { signal });
|
|
3793
|
-
if (result === undefined)
|
|
3794
|
-
throw new Error("Grok Bot polling ended before a result");
|
|
3795
|
-
return result;
|
|
3796
|
-
};
|
|
3788
|
+
var poll = (pass, intervalMs = 1000) => Effect6.tryPromise({ try: pass, catch: (cause) => cause }).pipe(Effect6.repeat({
|
|
3789
|
+
schedule: Schedule2.spaced(intervalMs),
|
|
3790
|
+
until: (value) => value !== undefined
|
|
3791
|
+
}));
|
|
3797
3792
|
var timeoutMessage = "Timed out waiting for the bot to reply through Cohall; the bot may still be running.";
|
|
3798
3793
|
var requireTime = (deadline) => {
|
|
3799
3794
|
if (Date.now() >= deadline)
|
|
@@ -3827,22 +3822,41 @@ var runGrokBot = async (path, task, signal) => {
|
|
|
3827
3822
|
return finishReply(existing);
|
|
3828
3823
|
const { deadline, command, dispatched } = await prepareBotReply(task);
|
|
3829
3824
|
requireTime(deadline);
|
|
3830
|
-
const waitForReply = () => poll(async () => {
|
|
3831
|
-
const receipt = await readBotReply(task.id);
|
|
3832
|
-
if (receipt !== undefined)
|
|
3833
|
-
return finishReply(receipt);
|
|
3834
|
-
requireTime(deadline);
|
|
3835
|
-
return;
|
|
3836
|
-
}, signal);
|
|
3837
|
-
if (dispatched)
|
|
3838
|
-
return waitForReply();
|
|
3839
|
-
if (path === undefined)
|
|
3840
|
-
throw new Error("Configure this device's Grok Bot gateway discovery file first");
|
|
3841
3825
|
const agentId = task.botId;
|
|
3842
3826
|
if (agentId === undefined)
|
|
3843
3827
|
throw new Error("Select a Grok Bot for this task");
|
|
3828
|
+
const lookup = async (lookupSignal = signal) => {
|
|
3829
|
+
if (path === undefined)
|
|
3830
|
+
throw new Error("Configure this device's Grok Bot gateway discovery file first");
|
|
3831
|
+
return rpc(await readConnection(path), "promptAcceptanceStatus", { accountSlot: "host", agentId, clientNonce: task.id }, Acceptance, lookupSignal);
|
|
3832
|
+
};
|
|
3833
|
+
const waitForReply = (reconcile = false) => {
|
|
3834
|
+
const receipt = poll(async () => {
|
|
3835
|
+
const receipt2 = await readBotReply(task.id);
|
|
3836
|
+
if (receipt2 !== undefined)
|
|
3837
|
+
return finishReply(receipt2);
|
|
3838
|
+
requireTime(deadline);
|
|
3839
|
+
return;
|
|
3840
|
+
});
|
|
3841
|
+
if (!reconcile || path === undefined)
|
|
3842
|
+
return Effect6.runPromise(receipt, { signal });
|
|
3843
|
+
const acceptance2 = poll(async (lookupSignal) => {
|
|
3844
|
+
const current = await lookup(lookupSignal).catch(() => {
|
|
3845
|
+
return;
|
|
3846
|
+
});
|
|
3847
|
+
if (current?.outcome !== "found")
|
|
3848
|
+
return;
|
|
3849
|
+
if (current.record.status === "rejected")
|
|
3850
|
+
throw new Error("Grok Bot rejected this task; check its conversation");
|
|
3851
|
+
return current.record.status === "accepted" ? true : undefined;
|
|
3852
|
+
}, 5000).pipe(Effect6.andThen(Effect6.never));
|
|
3853
|
+
return Effect6.runPromise(Effect6.raceFirst(receipt, acceptance2), { signal });
|
|
3854
|
+
};
|
|
3855
|
+
if (dispatched)
|
|
3856
|
+
return waitForReply(true);
|
|
3857
|
+
if (path === undefined)
|
|
3858
|
+
throw new Error("Configure this device's Grok Bot gateway discovery file first");
|
|
3844
3859
|
const connection = await readConnection(path);
|
|
3845
|
-
const lookup = () => rpc(connection, "promptAcceptanceStatus", { accountSlot: "host", agentId, clientNonce: task.id }, Acceptance, signal);
|
|
3846
3860
|
let acceptance = await lookup();
|
|
3847
3861
|
if (acceptance.outcome === "unknown-durability")
|
|
3848
3862
|
throw new Error("Grok Bot cannot confirm whether this task was accepted; check its conversation before retrying");
|
|
@@ -3860,7 +3874,7 @@ var runGrokBot = async (path, task, signal) => {
|
|
|
3860
3874
|
if (acceptance.outcome === "not-found") {
|
|
3861
3875
|
requireTime(deadline);
|
|
3862
3876
|
if (!await claimBotDispatch(task.id)) {
|
|
3863
|
-
return waitForReply();
|
|
3877
|
+
return waitForReply(true);
|
|
3864
3878
|
}
|
|
3865
3879
|
try {
|
|
3866
3880
|
await rpc(connection, "sendPrompt", {
|
|
@@ -3875,20 +3889,16 @@ var runGrokBot = async (path, task, signal) => {
|
|
|
3875
3889
|
const receipt2 = await readBotReply(task.id);
|
|
3876
3890
|
if (receipt2 !== undefined)
|
|
3877
3891
|
return finishReply(receipt2);
|
|
3878
|
-
|
|
3879
|
-
return;
|
|
3880
|
-
});
|
|
3881
|
-
if (reconciled?.outcome === "found" && reconciled.record.status === "rejected")
|
|
3882
|
-
throw new Error("Grok Bot rejected this task; check its conversation");
|
|
3883
|
-
return waitForReply();
|
|
3892
|
+
return waitForReply(true);
|
|
3884
3893
|
}
|
|
3894
|
+
return waitForReply();
|
|
3885
3895
|
}
|
|
3886
3896
|
}
|
|
3887
3897
|
if (acceptance.outcome === "unknown-durability")
|
|
3888
3898
|
throw new Error("Grok Bot cannot confirm task acceptance; the prompt was not resent");
|
|
3889
3899
|
if (acceptance.outcome === "found" && acceptance.record.status === "rejected")
|
|
3890
3900
|
throw new Error("Grok Bot rejected this task; check its conversation");
|
|
3891
|
-
return waitForReply();
|
|
3901
|
+
return waitForReply(acceptance.outcome === "found" && acceptance.record.status === "pending");
|
|
3892
3902
|
};
|
|
3893
3903
|
|
|
3894
3904
|
// apps/device/src/setup.ts
|
|
@@ -4116,31 +4126,18 @@ var linuxSystemExecutableRoots = [
|
|
|
4116
4126
|
];
|
|
4117
4127
|
var macosHomebrewRoots = ["/opt/homebrew", "/usr/local"];
|
|
4118
4128
|
var macosAdminGroup = 80;
|
|
4119
|
-
var
|
|
4129
|
+
var isTrustedSystemPath = (platform4, path) => platform4 === "linux" && (path === "/home" || linuxSystemExecutableRoots.some((root) => containsPath(root, path) || containsPath(path, root)));
|
|
4120
4130
|
var isTrustedGroupWritablePath = (options) => {
|
|
4121
4131
|
const writableRoot = options.writableRoot;
|
|
4122
4132
|
return options.platform === "darwin" && writableRoot !== undefined && options.uid !== undefined && options.ownerUid === options.uid && options.ownerGid === macosAdminGroup && macosHomebrewRoots.some((root) => containsPath(root, writableRoot)) && containsPath(writableRoot, options.canonical) && containsPath(writableRoot, options.path);
|
|
4123
4133
|
};
|
|
4124
|
-
var
|
|
4125
|
-
const
|
|
4126
|
-
let selected;
|
|
4127
|
-
for (const path of candidates) {
|
|
4128
|
-
const available2 = await access2(path, operatingSystem() === "win32" ? constants2.F_OK : constants2.X_OK).then(() => true).catch(() => false);
|
|
4129
|
-
if (available2) {
|
|
4130
|
-
selected = path;
|
|
4131
|
-
break;
|
|
4132
|
-
}
|
|
4133
|
-
}
|
|
4134
|
-
if (selected === undefined) {
|
|
4135
|
-
throw new Error(`Could not find ${command} on PATH`);
|
|
4136
|
-
}
|
|
4137
|
-
const canonical = await realpath2(selected);
|
|
4134
|
+
var validateExecutable = async (candidate, installationRoot) => {
|
|
4135
|
+
const canonical = await realpath2(candidate);
|
|
4138
4136
|
if (operatingSystem() === "win32") {
|
|
4139
4137
|
return canonical;
|
|
4140
4138
|
}
|
|
4141
4139
|
const uid = process.getuid?.();
|
|
4142
|
-
const writableRoot =
|
|
4143
|
-
const systemExecutable = isTrustedSystemExecutablePath(operatingSystem(), canonical);
|
|
4140
|
+
const writableRoot = installationRoot === undefined ? undefined : await realpath2(installationRoot);
|
|
4144
4141
|
for (let path = canonical;; path = dirname3(path)) {
|
|
4145
4142
|
const metadata = await stat(path);
|
|
4146
4143
|
const trustedGroupWritablePath = isTrustedGroupWritablePath({
|
|
@@ -4155,7 +4152,7 @@ var trustedExecutable = async (command, options = {}) => {
|
|
|
4155
4152
|
if ((metadata.mode & 2) !== 0 || (metadata.mode & 16) !== 0 && !trustedGroupWritablePath) {
|
|
4156
4153
|
throw new Error(`Refusing executable beneath group- or world-writable path ${path}`);
|
|
4157
4154
|
}
|
|
4158
|
-
if (uid !== undefined && metadata.uid !== 0 && metadata.uid !== uid && !
|
|
4155
|
+
if (uid !== undefined && metadata.uid !== 0 && metadata.uid !== uid && !isTrustedSystemPath(operatingSystem(), path)) {
|
|
4159
4156
|
throw new Error(`Refusing executable owned by another user at ${path}`);
|
|
4160
4157
|
}
|
|
4161
4158
|
const parent = dirname3(path);
|
|
@@ -4165,6 +4162,21 @@ var trustedExecutable = async (command, options = {}) => {
|
|
|
4165
4162
|
}
|
|
4166
4163
|
return canonical;
|
|
4167
4164
|
};
|
|
4165
|
+
var trustedExecutable = async (command, options = {}) => {
|
|
4166
|
+
const candidates = isAbsolute2(command) ? [command] : (process.env.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((directory) => executableNames(command).map((name) => join4(directory, name)));
|
|
4167
|
+
let failure;
|
|
4168
|
+
for (const path of candidates) {
|
|
4169
|
+
const available2 = await access2(path, operatingSystem() === "win32" ? constants2.F_OK : constants2.X_OK).then(() => true).catch(() => false);
|
|
4170
|
+
if (!available2)
|
|
4171
|
+
continue;
|
|
4172
|
+
try {
|
|
4173
|
+
return await validateExecutable(path, options.writableRoot);
|
|
4174
|
+
} catch (cause) {
|
|
4175
|
+
failure ??= cause;
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
throw failure ?? new Error(`Could not find ${command} on PATH`);
|
|
4179
|
+
};
|
|
4168
4180
|
var trustedServices = async (services, resolveExecutable) => {
|
|
4169
4181
|
const resolved = new Map;
|
|
4170
4182
|
const invocation = async (value) => {
|
|
@@ -4382,6 +4394,7 @@ var restartServices = async (runner, services, statePath, state, preserveDeviceR
|
|
|
4382
4394
|
};
|
|
4383
4395
|
var reportedRestartServices = (receipt) => receipt.restartingService === undefined || receipt.restartedServices.includes(receipt.restartingService) ? receipt.restartedServices : [...receipt.restartedServices, receipt.restartingService];
|
|
4384
4396
|
var upgrade = async (options) => {
|
|
4397
|
+
const target = normalizeUpgradeTarget(options.target);
|
|
4385
4398
|
const runner = options.runner ?? defaultRunner;
|
|
4386
4399
|
const resolveExecutable = options.resolveExecutable ?? trustedExecutable;
|
|
4387
4400
|
const runtimePlatform = options.platform ?? process.platform;
|
|
@@ -4389,7 +4402,7 @@ var upgrade = async (options) => {
|
|
|
4389
4402
|
const candidates = await trustedServices(serviceCandidates(runtimePlatform, uid), resolveExecutable);
|
|
4390
4403
|
const statePath = options.statePath ?? restartReceiptPath();
|
|
4391
4404
|
const previous = await readReceipt(statePath);
|
|
4392
|
-
if (previous?.version === options.currentVersion) {
|
|
4405
|
+
if (previous?.version === options.currentVersion && (target === "latest" || target === previous.version)) {
|
|
4393
4406
|
const byId = new Map(candidates.map((service) => [service.id, service]));
|
|
4394
4407
|
const pending = previous.pendingServices.flatMap((id) => {
|
|
4395
4408
|
const service = byId.get(id);
|
|
@@ -4442,15 +4455,12 @@ var upgrade = async (options) => {
|
|
|
4442
4455
|
dry_run: false
|
|
4443
4456
|
};
|
|
4444
4457
|
}
|
|
4445
|
-
await rm2(statePath, { force: true });
|
|
4446
|
-
const target = normalizeUpgradeTarget(options.target);
|
|
4447
4458
|
const entrypoint = options.entrypoint ?? process.argv[1];
|
|
4448
4459
|
if (entrypoint === undefined) {
|
|
4449
4460
|
throw new Error("Could not resolve the Cohall executable path");
|
|
4450
4461
|
}
|
|
4451
4462
|
const canonicalEntrypoint = await realpath2(entrypoint);
|
|
4452
4463
|
const installation = packageInstallation(canonicalEntrypoint, entrypoint);
|
|
4453
|
-
const install = packageInstallCommand(installation, target);
|
|
4454
4464
|
const services = await activeServices(runner, candidates);
|
|
4455
4465
|
await assertServiceInstallations(runner, services, canonicalEntrypoint, entrypoint);
|
|
4456
4466
|
if (options.dryRun) {
|
|
@@ -4466,14 +4476,21 @@ var upgrade = async (options) => {
|
|
|
4466
4476
|
dry_run: true
|
|
4467
4477
|
};
|
|
4468
4478
|
}
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4479
|
+
let nextVersion = await installedVersion(entrypoint).catch(() => {
|
|
4480
|
+
return;
|
|
4481
|
+
});
|
|
4482
|
+
if (nextVersion === undefined || target !== nextVersion) {
|
|
4483
|
+
const install = packageInstallCommand(installation, target);
|
|
4484
|
+
const installExecutable = options.resolveExecutable === undefined ? await trustedExecutable(install.command, installation.prefix === undefined ? {} : { writableRoot: installation.prefix }) : await resolveExecutable(install.command);
|
|
4485
|
+
await checked(runner, { ...install, command: installExecutable });
|
|
4486
|
+
nextVersion = await installedVersion(entrypoint);
|
|
4487
|
+
}
|
|
4472
4488
|
if (target !== "latest" && nextVersion !== target) {
|
|
4473
4489
|
throw new Error(`Installed Cohall ${nextVersion}, expected ${target}`);
|
|
4474
4490
|
}
|
|
4475
4491
|
const upgraded = nextVersion !== options.currentVersion;
|
|
4476
4492
|
if (!options.restart || services.length === 0) {
|
|
4493
|
+
await rm2(statePath, { force: true });
|
|
4477
4494
|
return {
|
|
4478
4495
|
upgraded,
|
|
4479
4496
|
from_version: options.currentVersion,
|
|
@@ -6373,4 +6390,4 @@ await main().catch((cause) => {
|
|
|
6373
6390
|
process.exitCode = 1;
|
|
6374
6391
|
});
|
|
6375
6392
|
|
|
6376
|
-
//# debugId=
|
|
6393
|
+
//# debugId=78963F23872A4D6564756E2164756E21
|