@commonlyai/cli 0.1.94 → 0.1.95

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonlyai/cli",
3
- "version": "0.1.94",
3
+ "version": "0.1.95",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The Commonly CLI — connect agents, manage pods, iterate fast",
6
6
  "type": "module",
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * OpenCode adapter — wraps `opencode run --format json` as a Commonly seat.
3
3
  *
4
- * Tested against opencode-ai 1.18.35. OpenCode stores conversation sessions
5
- * under XDG_DATA_HOME. Trusted seats use the operator's data home directly;
6
- * public seats get per-identity state without operator auth.json and a child
7
- * environment limited to safe launch/locale variables. Public provider keys
4
+ * Smoke-tested version is recorded in TESTED_OPENCODE_VERSION below.
5
+ * OpenCode stores sessions under XDG_DATA_HOME. Trusted seats use the
6
+ * operator's data home directly; public seats get per-identity state without
7
+ * operator auth.json and a child environment limited to safe launch/locale
8
+ * variables. Public provider keys
8
9
  * stay in the host adapter and reach the configured provider through a
9
10
  * per-spawn loopback proxy; OpenCode receives only that proxy's bearer.
10
11
  * This protects the key, not provider spend: the model can use its seat's key
@@ -56,6 +57,10 @@ import { resolvePublicSandboxMode } from '../sandbox/mode.js';
56
57
 
57
58
  const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
58
59
  const TERMINATION_GRACE_MS = 5000;
60
+ // Bump only with a passing real sandbox-exec smoke against the new version.
61
+ const TESTED_OPENCODE_VERSION = '1.18.35';
62
+ const ALLOW_UNTESTED_OPENCODE_VERSION_ENV = 'COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION';
63
+ const VERSION_CHECK_TIMEOUT_MS = 5000;
59
64
  const OPENCODE_CONFIG_GITIGNORE = [
60
65
  'node_modules',
61
66
  'package.json',
@@ -713,6 +718,47 @@ const spawnBinaryPath = (env) => {
713
718
  return 'opencode';
714
719
  };
715
720
 
721
+ const parseOpenCodeVersion = (output) => {
722
+ const match = String(output || '').trim().match(
723
+ /^(?:opencode\s+)?v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/i,
724
+ );
725
+ return match?.[1] || null;
726
+ };
727
+
728
+ const readOpenCodeVersion = (binary, env) => {
729
+ try {
730
+ const probeEnv = { ...env };
731
+ for (const key of DISABLED_ENV) probeEnv[key] = '1';
732
+ const result = spawnSync(binary, ['--version'], {
733
+ encoding: 'utf8', env: probeEnv, timeout: VERSION_CHECK_TIMEOUT_MS,
734
+ });
735
+ if (result.error || result.status !== 0) return null;
736
+ return parseOpenCodeVersion(result.stdout);
737
+ } catch {
738
+ return null;
739
+ }
740
+ };
741
+
742
+ const compareOpenCodeVersions = (left, right) => {
743
+ const leftParts = left.split(/[+-]/, 1)[0].split('.').map(Number);
744
+ const rightParts = right.split(/[+-]/, 1)[0].split('.').map(Number);
745
+ for (let index = 0; index < 3; index += 1) {
746
+ if (leftParts[index] !== rightParts[index]) {
747
+ return leftParts[index] > rightParts[index] ? 1 : -1;
748
+ }
749
+ }
750
+ return 0;
751
+ };
752
+
753
+ const describeOpenCodeVersionMismatch = (installedVersion) => {
754
+ if (!installedVersion) return 'installed OpenCode version is unknown';
755
+ const relation = compareOpenCodeVersions(installedVersion, TESTED_OPENCODE_VERSION);
756
+ if (relation === 0) {
757
+ return `installed OpenCode ${installedVersion} differs from smoke-tested ${TESTED_OPENCODE_VERSION}`;
758
+ }
759
+ return `installed OpenCode ${installedVersion} is ${relation > 0 ? 'newer' : 'older'} than smoke-tested ${TESTED_OPENCODE_VERSION}`;
760
+ };
761
+
716
762
  export default {
717
763
  name: 'opencode',
718
764
  runtimeType: 'opencode',
@@ -726,8 +772,7 @@ export default {
726
772
  const res = spawnSync('opencode', ['--version'], { encoding: 'utf8' });
727
773
  if (res.error || res.status !== 0) return null;
728
774
  const stdout = (res.stdout || '').trim();
729
- const versionMatch = stdout.match(/(\d+\.\d+(?:\.\d+)?)/);
730
- const version = versionMatch ? versionMatch[1] : (stdout || 'unknown');
775
+ const version = parseOpenCodeVersion(stdout) || stdout || 'unknown';
731
776
  const where = spawnSync('which', ['opencode'], { encoding: 'utf8' });
732
777
  const path = where.status === 0 ? (where.stdout || '').trim() || 'opencode' : 'opencode';
733
778
  return { path, version };
@@ -761,6 +806,29 @@ export default {
761
806
  }
762
807
  const providerKeyFile = await validateProviderEnvironment(ctx.environment, ctx.cwd);
763
808
 
809
+ const sourceEnv = ctx.env || process.env;
810
+ const childSourceEnv = Object.fromEntries(Object.entries(sourceEnv)
811
+ .filter(([key]) => key !== ALLOW_UNTESTED_OPENCODE_VERSION_ENV));
812
+ const runtimeEnv = isPublic
813
+ ? Object.fromEntries(Object.entries(childSourceEnv).filter(([key]) => PUBLIC_SAFE_ENV.test(key)))
814
+ : childSourceEnv;
815
+ const binary = ctx._binaryPath || spawnBinaryPath(runtimeEnv);
816
+ const installedVersion = readOpenCodeVersion(binary, runtimeEnv);
817
+ if (installedVersion !== TESTED_OPENCODE_VERSION) {
818
+ const mismatch = describeOpenCodeVersionMismatch(installedVersion);
819
+ const allowOverride = process.env[ALLOW_UNTESTED_OPENCODE_VERSION_ENV] === '1';
820
+ if (isPublic && !allowOverride) {
821
+ throw new Error(
822
+ `Public OpenCode seats refuse to run: ${mismatch}. `
823
+ + `To override from the host process, set ${ALLOW_UNTESTED_OPENCODE_VERSION_ENV}=1.`,
824
+ );
825
+ }
826
+ // eslint-disable-next-line no-console
827
+ console.warn(isPublic
828
+ ? `[opencode] ${mismatch}; continuing because the host process set ${ALLOW_UNTESTED_OPENCODE_VERSION_ENV}=1`
829
+ : `[opencode] ${mismatch}; trusted seat continues`);
830
+ }
831
+
764
832
  const fullPrompt = buildMemoryPreamble(prompt, ctx.memoryLongTerm, {
765
833
  freshSession: !ctx.sessionId,
766
834
  });
@@ -834,10 +902,6 @@ export default {
834
902
  });
835
903
  await chmod(configPath, 0o600);
836
904
 
837
- const sourceEnv = ctx.env || process.env;
838
- const runtimeEnv = isPublic
839
- ? Object.fromEntries(Object.entries(sourceEnv).filter(([key]) => PUBLIC_SAFE_ENV.test(key)))
840
- : sourceEnv;
841
905
  const childEnv = makeChildEnvironment({
842
906
  source: runtimeEnv,
843
907
  tempDir,
@@ -858,7 +922,6 @@ export default {
858
922
  effort: ctx.environment?.effort,
859
923
  title: ctx.agentName,
860
924
  });
861
- const binary = ctx._binaryPath || spawnBinaryPath(childEnv);
862
925
  const platform = ctx._platform || process.platform;
863
926
  let cmd = binary;
864
927
  let spawnArgs = args;
@@ -928,13 +991,16 @@ export default {
928
991
  };
929
992
 
930
993
  export {
994
+ ALLOW_UNTESTED_OPENCODE_VERSION_ENV,
931
995
  DISABLED_ENV,
996
+ TESTED_OPENCODE_VERSION,
932
997
  buildArgs,
933
998
  buildMcpConfig,
934
999
  buildProviderConfig,
935
1000
  makeEventParser,
936
1001
  prepareOpenCodeDataHome,
937
1002
  publicPermissions,
1003
+ readOpenCodeVersion,
938
1004
  qualifiedProviderModel,
939
1005
  resolveProviderKeyFile,
940
1006
  startProviderProxy,