@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 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.0", 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$/, {
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 = async (pass, signal) => {
3789
- const result = await Effect6.runPromise(Effect6.tryPromise({ try: pass, catch: (cause) => cause }).pipe(Effect6.repeat({
3790
- schedule: Schedule2.spaced("1 second"),
3791
- until: (value) => value !== undefined
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
- const reconciled = await lookup().catch(() => {
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 isTrustedSystemExecutablePath = (platform4, canonical) => platform4 === "linux" && linuxSystemExecutableRoots.some((root) => containsPath(root, canonical));
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 trustedExecutable = async (command, options = {}) => {
4125
- const candidates = isAbsolute2(command) ? [command] : (process.env.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((directory) => executableNames(command).map((name) => join4(directory, name)));
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 = options.writableRoot === undefined ? undefined : await realpath2(options.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 && !systemExecutable) {
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
- const installExecutable = options.resolveExecutable === undefined ? await trustedExecutable(install.command, installation.prefix === undefined ? {} : { writableRoot: installation.prefix }) : await resolveExecutable(install.command);
4470
- await checked(runner, { ...install, command: installExecutable });
4471
- const nextVersion = await installedVersion(entrypoint);
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=F147BDC9F8E0C7EE64756E2164756E21
6393
+ //# debugId=78963F23872A4D6564756E2164756E21