@commonlyai/cli 0.1.93 → 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.93",
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
  });
@@ -769,9 +837,12 @@ export default {
769
837
  const configPath = join(tempDir, 'opencode.json');
770
838
  const configDir = join(tempDir, 'config-dir');
771
839
  const xdgConfig = join(tempDir, 'xdg-config');
772
- // OpenCode initializes Path.config with mkdir and ensureGitignore during
773
- // startup. Seatbelt grants this per-spawn tree read-only, so create both
774
- // config roots and the exact ignore files it would otherwise write.
840
+ const home = isPublic ? join(tempDir, 'home') : null;
841
+ // OpenCode initializes its global and XDG config directories during
842
+ // startup, so create those roots and their ignore files before wrapping.
843
+ // Keep public HOME per-spawn, but leave $HOME/.opencode absent: OpenCode
844
+ // loads it after OPENCODE_CONFIG when present, which could persist config
845
+ // or MCP injection into later spawns.
775
846
  const xdgConfigDir = join(xdgConfig, 'opencode');
776
847
  await Promise.all([
777
848
  mkdir(configDir),
@@ -831,10 +902,6 @@ export default {
831
902
  });
832
903
  await chmod(configPath, 0o600);
833
904
 
834
- const sourceEnv = ctx.env || process.env;
835
- const runtimeEnv = isPublic
836
- ? Object.fromEntries(Object.entries(sourceEnv).filter(([key]) => PUBLIC_SAFE_ENV.test(key)))
837
- : sourceEnv;
838
905
  const childEnv = makeChildEnvironment({
839
906
  source: runtimeEnv,
840
907
  tempDir,
@@ -843,7 +910,7 @@ export default {
843
910
  dataHome: openCodeHome.dataHome,
844
911
  stateHome,
845
912
  cacheHome,
846
- home: isPublic ? openCodeHome.root : null,
913
+ home,
847
914
  tempWork,
848
915
  credentialFile: credential?.path || null,
849
916
  });
@@ -855,7 +922,6 @@ export default {
855
922
  effort: ctx.environment?.effort,
856
923
  title: ctx.agentName,
857
924
  });
858
- const binary = ctx._binaryPath || spawnBinaryPath(childEnv);
859
925
  const platform = ctx._platform || process.platform;
860
926
  let cmd = binary;
861
927
  let spawnArgs = args;
@@ -925,13 +991,16 @@ export default {
925
991
  };
926
992
 
927
993
  export {
994
+ ALLOW_UNTESTED_OPENCODE_VERSION_ENV,
928
995
  DISABLED_ENV,
996
+ TESTED_OPENCODE_VERSION,
929
997
  buildArgs,
930
998
  buildMcpConfig,
931
999
  buildProviderConfig,
932
1000
  makeEventParser,
933
1001
  prepareOpenCodeDataHome,
934
1002
  publicPermissions,
1003
+ readOpenCodeVersion,
935
1004
  qualifiedProviderModel,
936
1005
  resolveProviderKeyFile,
937
1006
  startProviderProxy,