Repository: stateful/vscode-runme (ts)

Issue:
Use cell uuid for terminal identification
We should use cell uuid instead of cell index for terminal identification. 

This prevents weird issues, like The "Open Terminal" button appearing on Markdown cells (reported over slack by @sourishkrout)

Pull request description:
Use cell uuid for terminal identification
Fixes #434 

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a06204bd..e781a31a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -57,6 +57,8 @@ To get the code base, have [git](https://git-scm.com/downloads) installed and ru
 
 ```sh
 git clone git@github.com:stateful/vscode-runme.git
+cd vscode-runme
+nvm install
 ```
 
 optionally install the GitHub CLI to streamline downloading the WASM binary:
@@ -69,15 +71,13 @@ brew install gh
 
 make sure to configure your local npm to pull from Buf's registry (for GRPC dependencies)
 
-```sh
+```sh { name=configureNPM }
 npm config set @buf:registry https://buf.build/gen/npm/v1
 ```
 
-then ensure to install all project dependencies:
+then ensure to install all project dependencies.
 
-```sh
-cd vscode-runme
-nvm install
+```sh { name=setup }
 GITHUB_REF_NAME=$(git branch --show-current) GITHUB_TOKEN=$(gh auth token) npm install --include=dev
 ```
 
@@ -93,7 +93,7 @@ DOWNLOAD_URL=$(node .github/scripts/getLastStableRelease) TYPE=tar npm run prepa
 
 To compile all extension files, run:
 
-```sh
+```sh { name=build }
 npm run build
 ```
 
@@ -101,7 +101,7 @@ npm run build
 
 To test the project, run:
 
-```sh
+```sh { name=test }
 npm run test
 ```
 
diff --git a/__mocks__/vscode.ts b/__mocks__/vscode.ts
index 207b4b22..48b10869 100644
--- a/__mocks__/vscode.ts
+++ b/__mocks__/vscode.ts
@@ -25,7 +25,7 @@ export class Uri extends URI {
   static parse = vi.fn(super.parse)
 
   static joinPath = vi.fn((uri: Uri, ...paths: string[]) => {
-    return Uri.file(path.join(uri.fsPath, ...paths))
+    return super.file(path.join(uri.fsPath, ...paths))
   })
 }
 
diff --git a/package.json b/package.json
index fca939e1..23edee4a 100644
--- a/package.json
+++ b/package.json
@@ -261,18 +261,6 @@
       {
         "title": "Runme",
         "properties": {
-          "runme.shell.interactive": {
-            "type": "boolean",
-            "scope": "machine",
-            "default": true,
-            "markdownDescription": "If enabled all shell scripts are run as interactive VS Code tasks, if set to `false` they are executed within the kernel and output is set in resulting cell."
-          },
-          "runme.shell.closeTerminalOnSuccess": {
-            "type": "boolean",
-            "scope": "machine",
-            "default": true,
-            "markdownDescription": "If enabled, the terminal closes if task succeeds running."
-          },
           "runme.flags.disableSaveRestriction": {
             "type": "boolean",
             "scope": "machine",
@@ -308,11 +296,10 @@
             "default": 60000,
             "markdownDescription": "Timeout until Runme will cancel the clone process."
           },
-          "runme.server.port": {
-            "type": "number",
+          "runme.server.customAddress": {
+            "type": "string",
             "scope": "machine",
-            "default": 7863,
-            "markdownDescription": "Port used by Runme server. (It requires the GRPC interface enabled)"
+            "markdownDescription": "Custom runme server address. This address will be used instead of launching the server at start. Use this only if you know what you are doing!"
           },
           "runme.server.binaryPath": {
             "type": "string",
diff --git a/src/constants.ts b/src/constants.ts
index 3b9e7ed0..f72ef8ae 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -88,3 +88,4 @@ export const LANGUAGES = new Map([
 ].map(([, source, dest]) => [source, dest]))
 
 export const SERVER_ADDRESS = 'localhost'
+export const SERVER_PORT = 7863
diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts
index 86dc0972..affab2be 100644
--- a/src/extension/commands/index.ts
+++ b/src/extension/commands/index.ts
@@ -1,5 +1,4 @@
 import path from 'node:path'
-import os from 'node:os'
 
 import {
   NotebookCell, Uri, window, env, NotebookDocument, TextDocument, ViewColumn,
@@ -7,7 +6,7 @@ import {
 } from 'vscode'
 import { v4 as uuidv4 } from 'uuid'
 
-import { getBinaryPath, getTLSDir, getTLSEnabled, isNotebookTerminalEnabledForCell } from '../../utils/configuration'
+import { getTLSDir, getTLSEnabled, isNotebookTerminalEnabledForCell } from '../../utils/configuration'
 import { Kernel } from '../kernel'
 import { getAnnotations, getTerminalByCell, openFileAsRunmeNotebook } from '../utils'
 import RunmeServer from '../server/runmeServer'
@@ -78,8 +77,6 @@ export function runCLICommand(
 
     const envs: Record<string, string> = { }
 
-    const runmeExecutable = getBinaryPath(extensionBaseUri, os.platform()).fsPath
-
     if (grpcRunner) {
       envs['RUNME_SERVER_ADDR'] = server.address()
 
@@ -103,7 +100,7 @@ export function runCLICommand(
     })
 
     term.show(false)
-    term.sendText(`${runmeExecutable} run ${annotations.name} ${args.join(' ')}`)
+    term.sendText(`runme run ${annotations.name} ${args.join(' ')}`)
   }
 }
 
diff --git a/src/extension/executors/runner.ts b/src/extension/executors/runner.ts
index 99fde2c8..e4cbb6fc 100644
--- a/src/extension/executors/runner.ts
+++ b/src/extension/executors/runner.ts
@@ -20,7 +20,7 @@ import { ClientMessages } from '../../constants'
 import { ClientMessage } from '../../types'
 import { PLATFORM_OS } from '../constants'
 import { IRunner, IRunnerEnvironment, RunProgramExecution } from '../runner'
-import { getAnnotations, getCmdShellSeq, getTerminalByCell, prepareCmdSeq } from '../utils'
+import { getAnnotations, getCellRunmeId, getCmdShellSeq, getTerminalByCell, prepareCmdSeq } from '../utils'
 import { postClientMessage } from '../../utils/messaging'
 import { isNotebookTerminalEnabledForCell } from '../../utils/configuration'
 import { Kernel } from '../kernel'
@@ -71,7 +71,7 @@ export async function executeRunner(
 
   const cwd = path.dirname(runningCell.uri.fsPath)
 
-  const RUNME_ID = `${runningCell.fileName}:${exec.cell.index}`
+  const RUNME_ID = getCellRunmeId(exec.cell)
 
   let cellText = exec.cell.document.getText()
 
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 5f40717d..6fb1b672 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -2,6 +2,8 @@
 import { workspace, notebooks, commands, ExtensionContext, tasks, window, Uri } from 'vscode'
 import { TelemetryReporter } from 'vscode-telemetry'
 
+import { registerExtensionEnvironmentVariables } from '../utils/configuration'
+
 import { Kernel } from './kernel'
 import RunmeServer from './server/runmeServer'
 import RunmeServerError from './server/runmeServerError'
@@ -65,7 +67,10 @@ export class RunmeExtension {
         return window.showErrorMessage(`Failed to start Runme server. Reason: ${e.message}`)
       }
       TelemetryReporter.sendTelemetryErrorEvent('extension.server', { data: (e as Error).message })
-      return window.showErrorMessage('Failed to start Runme server, please try to reload the window')
+      return window.showErrorMessage(
+        'Failed to start Runme server, please try to reload the window. ' +
+        `Reason: ${(e as any).message}`
+      )
     }
 
     const treeViewer = new RunmeLauncherProvider(getDefaultWorkspace())
@@ -78,6 +83,8 @@ export class RunmeExtension {
 
     const codeLensProvider = new RunmeCodeLensProvider(serializer, runCLI, winCodeLensRunSurvey, runner, kernel)
 
+    registerExtensionEnvironmentVariables(context)
+
     context.subscriptions.push(
       kernel,
       serializer,
diff --git a/src/extension/grpc/serializerTypes.ts b/src/extension/grpc/serializerTypes.ts
index c83101b3..561d9bca 100644
--- a/src/extension/grpc/serializerTypes.ts
+++ b/src/extension/grpc/serializerTypes.ts
@@ -1,11 +1,1 @@
-import {
-  DeserializeRequest,
-  SerializeRequest,
-  Notebook,
-} from '@buf/stateful_runme.community_timostamm-protobuf-ts/runme/parser/v1/parser_pb'
-
-export {
-  DeserializeRequest,
-  SerializeRequest,
-  Notebook,
-}
+export * from '@buf/stateful_runme.community_timostamm-protobuf-ts/runme/parser/v1/parser_pb'
diff --git a/src/extension/server/runmeServer.ts b/src/extension/server/runmeServer.ts
index b0e1ff1f..13d8f137 100644
--- a/src/extension/server/runmeServer.ts
+++ b/src/extension/server/runmeServer.ts
@@ -11,7 +11,14 @@ import {
   HealthCheckResponse_ServingStatus
 } from '../grpc/healthTypes'
 import { SERVER_ADDRESS } from '../../constants'
-import { enableServerLogs, getBinaryPath, getPortNumber, getTLSDir, getTLSEnabled } from '../../utils/configuration'
+import {
+  enableServerLogs,
+  getBinaryPath,
+  getCustomServerAddress,
+  getPortNumber,
+  getTLSDir,
+  getTLSEnabled
+} from '../../utils/configuration'
 import { isPortAvailable } from '../utils'
 import { HealthClient } from '../grpc/client'
 
@@ -39,6 +46,7 @@ class RunmeServer implements Disposable {
     #disposables: Disposable[] = []
     #transport?: GrpcTransport
     #serverDisposables: Disposable[] = []
+    #forceExternalServer: boolean
 
     readonly #onClose = this.register(new EventEmitter<{ code: number|null }>())
     readonly #onTransportReady = this.register(new EventEmitter<{ transport: GrpcTransport }>())
@@ -49,7 +57,7 @@ class RunmeServer implements Disposable {
     constructor(
       extBasePath: Uri,
       options: IServerConfig,
-      protected readonly externalServer: boolean,
+      externalServer: boolean,
       protected readonly enableRunner = false
     ) {
       this.#port = getPortNumber()
@@ -59,6 +67,7 @@ class RunmeServer implements Disposable {
       this.#maxNumberOfIntents = options.maxNumberOfIntents
       this.#acceptsIntents = options.acceptsConnection?.intents || 50
       this.#acceptsInterval = options.acceptsConnection?.interval || 200
+      this.#forceExternalServer = externalServer
     }
 
     dispose() {
@@ -98,14 +107,22 @@ class RunmeServer implements Disposable {
     }
 
     address() {
-      return `${SERVER_ADDRESS}:${this.#port}`
+      return getCustomServerAddress() || `${SERVER_ADDRESS}:${this.#port}`
+    }
+
+    private get externalServer(): boolean {
+      return !!(getCustomServerAddress() || this.#forceExternalServer)
     }
 
     private static async getTLS(tlsDir: string) {
-      const certPEM = await fs.readFile(path.join(tlsDir, 'cert.pem'))
-      const privKeyPEM = await fs.readFile(path.join(tlsDir, 'key.pem'))
+      try {
+        const certPEM = await fs.readFile(path.join(tlsDir, 'cert.pem'))
+        const privKeyPEM = await fs.readFile(path.join(tlsDir, 'key.pem'))
 
-      return { certPEM, privKeyPEM }
+        return { certPEM, privKeyPEM }
+      } catch(e: any) {
+        throw new RunmeServerError('Unable to read TLS files', e)
+      }
     }
 
     protected async channelCredentials(): Promise<ChannelCredentials> {
diff --git a/src/extension/server/runmeServerError.ts b/src/extension/server/runmeServerError.ts
index c49d9a6f..a5945516 100644
--- a/src/extension/server/runmeServerError.ts
+++ b/src/extension/server/runmeServerError.ts
@@ -1,8 +1,11 @@
 class RunmeServerError extends Error {
-    constructor(message: string) {
-        super(message)
-        this.name = 'RunmeServerError'
+    constructor(message: string, err?: Error) {
+      if (err) {
+        message += `\nCaused by: ${err?.message}`
+      }
+      super(message)
+      this.name = 'RunmeServerError'
     }
 }
 
-export default RunmeServerError
\ No newline at end of file
+export default RunmeServerError
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index d0780c5f..fc959ef9 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -36,7 +36,6 @@ const HASH_PREFIX_REGEXP = /^\s*\#\s*/g
 export function getAnnotations(cell: vscode.NotebookCell): CellAnnotations
 export function getAnnotations(metadata?: Serializer.Metadata): CellAnnotations
 export function getAnnotations(raw: unknown): CellAnnotations | undefined {
-  const config = vscode.workspace.getConfiguration('runme.shell')
   const metadataFromCell = raw as vscode.NotebookCell
   let metadata = raw as Serializer.Metadata
 
@@ -45,8 +44,6 @@ export function getAnnotations(raw: unknown): CellAnnotations | undefined {
   }
 
   const schema = {
-    interactive: config.get<boolean>('interactive'),
-    closeTerminalOnSuccess: config.get<boolean>('closeTerminalOnSuccess'),
     ...metadata,
     name:
       metadata.name ||
@@ -60,7 +57,6 @@ export function getAnnotations(raw: unknown): CellAnnotations | undefined {
 }
 
 export function validateAnnotations(cell: NotebookCell): CellAnnotationsErrorResult {
-  const config = vscode.workspace.getConfiguration('runme.shell')
   let metadata = cell as Serializer.Metadata
 
   if (cell.metadata) {
@@ -68,8 +64,6 @@ export function validateAnnotations(cell: NotebookCell): CellAnnotationsErrorRes
   }
 
   const schema = {
-    interactive: config.get<boolean>('interactive'),
-    closeTerminalOnSuccess: config.get<boolean>('closeTerminalOnSuccess'),
     ...metadata,
     name:
       metadata.name ||
@@ -99,9 +93,23 @@ export function getTerminalRunmeId(t: vscode.Terminal): string|undefined {
     ?? undefined
 }
 
+export function getCellRunmeId(cell: vscode.NotebookCell) {
+  return getCellUUID(cell)
+}
+
+function getCellUUID(cell: vscode.NotebookCell): string {
+  if (cell.kind !== vscode.NotebookCellKind.Code) {
+    throw new Error('Cannot get cell UUID for non-code cell!')
+  }
+
+  return getAnnotations(cell)['runme.dev/uuid']!
+}
+
 export function getTerminalByCell(cell: vscode.NotebookCell): RunmeTerminal | undefined {
+  const RUNME_ID = getCellRunmeId(cell)
+
   return vscode.window.terminals.find((t) => {
-    return getTerminalRunmeId(t) === `${cell.document.fileName}:${cell.index}`
+    return getTerminalRunmeId(t) === RUNME_ID
   }) as RunmeTerminal | undefined
 }
 
diff --git a/src/schema.ts b/src/schema.ts
index 56610448..1d2026a7 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -63,10 +63,10 @@ export const AnnotationSchema = {
 export const SafeCellAnnotationsSchema = z.object({
     ...AnnotationSchema,
     background: falseyBoolean(false),
-    interactive: falseyBoolean(false),
+    interactive: falseyBoolean(true),
     closeTerminalOnSuccess: falseyBoolean(true),
 })
 
 export const CellAnnotationsSchema = z.object({
     ...AnnotationSchema
-})
\ No newline at end of file
+})
diff --git a/src/types.ts b/src/types.ts
index 69ab4efd..28e4e4bf 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -4,6 +4,7 @@ import { z } from 'zod'
 import { OutputType, ClientMessages } from './constants'
 import { SafeCellAnnotationsSchema } from './schema'
 import type { IRunnerProgramSession } from './extension/runner'
+import type * as Grpc from './extension/grpc/serializerTypes'
 
 export namespace Serializer {
   export type Notebook = {
@@ -11,19 +12,10 @@ export namespace Serializer {
     metadata?: Metadata
   }
 
-  type TextPosition = number
-
-  export type TextRange = {
-    start: TextPosition
-    end: TextPosition
-  }
-
-  export type Cell = {
+  export type Cell = Omit<Grpc.Cell, 'kind'|'metadata'|'languageId'> & {
     metadata?: Metadata
+    kind: NotebookCellKind
     languageId?: string
-    value: string
-    textRange?: Serializer.TextRange
-    kind: NotebookCellKind.Markup | NotebookCellKind.Code
   }
 
   export interface Wasm {
diff --git a/src/utils/configuration.ts b/src/utils/configuration.ts
index da32fe44..55a440fe 100644
--- a/src/utils/configuration.ts
+++ b/src/utils/configuration.ts
@@ -1,11 +1,12 @@
 import path from 'node:path'
 import os from 'node:os'
 
-import { NotebookCell, Uri, workspace } from 'vscode'
+import { ExtensionContext, NotebookCell, Uri, workspace } from 'vscode'
 import { z } from 'zod'
 import { v4 as uuidv4 } from 'uuid'
 
 import { getAnnotations, isWindows } from '../extension/utils'
+import { SERVER_PORT } from '../constants'
 
 const SERVER_SECTION_NAME = 'runme.server'
 const TERMINAL_SECTION_NAME= 'runme.terminal'
@@ -16,12 +17,10 @@ type NotebookTerminalValue = keyof typeof configurationSchema.notebookTerminal
 
 const configurationSchema = {
     server: {
-        port: z
-            .number()
-            .positive()
-            .min(7070)
-            .max(9090)
-            .default(7863),
+        customAddress: z
+            .string()
+            .nonempty()
+            .optional(),
         binaryPath: z
             .string()
             .optional(),
@@ -80,7 +79,11 @@ const getCodeLensConfigurationValue = <T>(configName: keyof typeof configuration
 }
 
 const getPortNumber = (): number => {
-    return getServerConfigurationValue<number>('port', 7863)
+  return SERVER_PORT
+}
+
+const getCustomServerAddress = (): string|undefined => {
+  return getServerConfigurationValue<string|undefined>('customAddress', undefined)
 }
 
 const getTLSEnabled = (): boolean => {
@@ -148,6 +151,15 @@ const getCodeLensEnabled = (): boolean => {
   return getCodeLensConfigurationValue<boolean>('enable', true)
 }
 
+const registerExtensionEnvironmentVariables = (context: ExtensionContext): void => {
+  context.environmentVariableCollection.prepend(
+    'PATH',
+    path.dirname(
+      getBinaryPath(context.extensionUri, os.platform()).fsPath
+    ) + (isWindows() ? ';' : ':'),
+  )
+}
+
 export {
     getPortNumber,
     getBinaryPath,
@@ -161,4 +173,6 @@ export {
     getNotebookTerminalFontSize,
     getNotebookTerminalRows,
     getCodeLensEnabled,
+    registerExtensionEnvironmentVariables,
+    getCustomServerAddress,
 }


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index c34ba7bb..fafadaaa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -9,21 +9,19 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
       - uses: actions/checkout@v3
-      - uses: actions/setup-node@v3
+      - name: 🧪 Setup and Test with Runme
+        uses: coactions/setup-xvfb@v1
         with:
-          node-version: 18
-      - name: 📦 Install Dependencies
-        run: npm install
+          run: |
+            npm i -g runme
+            npx runme run --filename=CONTRIBUTING.md configureNPM
+            npx runme run --filename=CONTRIBUTING.md setup
+            npx runme run --filename=CONTRIBUTING.md build
+            npx runme run --filename=CONTRIBUTING.md test
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      - name: 🏗 Build Project
-        run: npm run build:dev
-      - name: 🧪 Run Tests
-        uses: GabrielBB/xvfb-action@v1
-        with:
-          run: npm test
-          options: "-screen 0 1600x1200x24"
-      - uses: actions/upload-artifact@v3
+      - name: 🔼 Upload Artifacts
+        uses: actions/upload-artifact@v3
         if: failure()
         with:
           name: screenshots
diff --git a/tests/extension/commands/index.test.ts b/tests/extension/commands/index.test.ts
index c7534932..bcacc6d0 100644
--- a/tests/extension/commands/index.test.ts
+++ b/tests/extension/commands/index.test.ts
@@ -119,7 +119,7 @@ test('runCLICommand', async () => {
 
   await runCLICommand({} as any, false, {} as any, {} as any)(cell)
   expect(vi.mocked((terminal as any).sendText)).toHaveBeenCalledWith(
-    '/bin/runme run foo-bar --chdir="/foo/bar" --filename="README.md"'
+    'runme run foo-bar --chdir="/foo/bar" --filename="README.md"'
   )
 })
 
diff --git a/tests/extension/configuration.test.ts b/tests/extension/configuration.test.ts
index 6e8ec074..9ca32129 100644
--- a/tests/extension/configuration.test.ts
+++ b/tests/extension/configuration.test.ts
@@ -14,6 +14,7 @@ import {
   getNotebookTerminalFontSize,
   getCodeLensEnabled
 } from '../../src/utils/configuration'
+import { SERVER_PORT } from '../../src/constants'
 
 vi.mock('../../src/extension/grpc/client', () => ({}))
 vi.mock('../../src/extension/grpc/runnerTypes', () => ({}))
@@ -100,9 +101,8 @@ suite('Configuration', () => {
     })
 
     test('Should use a valid specified port number', () => {
-        SETTINGS_MOCK.port = 8080
         const portNumber = getPortNumber()
-        expect(portNumber).toStrictEqual(8080)
+        expect(portNumber).toStrictEqual(SERVER_PORT)
     })
 
     test('Should disable server logs with an invalid value', () => {
diff --git a/tests/extension/extension.test.ts b/tests/extension/extension.test.ts
index 06b33ba9..05b7e686 100644
--- a/tests/extension/extension.test.ts
+++ b/tests/extension/extension.test.ts
@@ -47,9 +47,18 @@ test('initializes all providers', async () => {
   vi.mocked(workspace.getConfiguration).mockReturnValue({
     get: vi.fn((config: string) => configValues[config])
   } as any)
-  vi.mocked(Uri.joinPath).mockReturnValue('/foo/bar' as any)
+  const dummyFilePath = Uri.file('/foo/bar')
+  vi.mocked(Uri.joinPath).mockReturnValue(dummyFilePath)
   RunmeServer['getTLS'] = vi.fn().mockResolvedValue({ privKeyPEM: testPrivKeyPEM, certPEM: testCertPEM })
-  const context: any = { subscriptions: [], extensionUri: { fsPath: '/foo/bar' } }
+  const context: any = {
+    subscriptions: [],
+    extensionUri: { fsPath: '/foo/bar' },
+    environmentVariableCollection: {
+      prepend: vi.fn(),
+      append: vi.fn(),
+      replace: vi.fn(),
+    }
+  }
   const ext = new RunmeExtension()
   await ext.initialize(context)
   expect(notebooks.registerNotebookCellStatusBarItemProvider).toBeCalledTimes(6)
@@ -58,7 +67,7 @@ test('initializes all providers', async () => {
   expect(window.registerTreeDataProvider).toBeCalledTimes(1)
   expect(window.registerUriHandler).toBeCalledTimes(1)
 
-  expect(commands.executeCommand).toBeCalledWith('vscode.openWith', '/foo/bar', 'runme')
-  expect(workspace.fs.stat).toBeCalledWith('/foo/bar')
-  expect(workspace.fs.delete).toBeCalledWith('/foo/bar')
+  expect(commands.executeCommand).toBeCalledWith('vscode.openWith', dummyFilePath, 'runme')
+  expect(workspace.fs.stat).toBeCalledWith(dummyFilePath)
+  expect(workspace.fs.delete).toBeCalledWith(dummyFilePath)
 })
diff --git a/tests/extension/schema.test.ts b/tests/extension/schema.test.ts
index 729ae378..74597da0 100644
--- a/tests/extension/schema.test.ts
+++ b/tests/extension/schema.test.ts
@@ -93,7 +93,7 @@ suite('AnnotationSchema', () => {
         test('Should add safe defaults for invalid values', () => {
             const expectedOutput = {
                 background: false,
-                interactive: false,
+                interactive: true,
                 closeTerminalOnSuccess: true
             }
             const parseResult = SafeCellAnnotationsSchema.safeParse(
@@ -119,7 +119,7 @@ suite('AnnotationSchema', () => {
                 const { background, closeTerminalOnSuccess, interactive, mimeType, name } = parseResult.data
                 expect(background).toBeFalsy()
                 expect(closeTerminalOnSuccess).toBeTruthy()
-                expect(interactive).toBeFalsy()
+                expect(interactive).toBeTruthy()
                 expect(mimeType).toStrictEqual('text/plain')
                 expect(name).toBe('')
             }
@@ -151,10 +151,10 @@ suite('AnnotationSchema', () => {
                 const { background, closeTerminalOnSuccess, interactive, mimeType, name } = parseResult.data
                 expect(background).toBeFalsy()
                 expect(closeTerminalOnSuccess).toBeTruthy()
-                expect(interactive).toBeFalsy()
+                expect(interactive).toBeTruthy()
                 expect(mimeType).toStrictEqual('text/plain')
                 expect(name).toBe('')
             }
         })
     })
-})
\ No newline at end of file
+})
diff --git a/tests/extension/utils.test.ts b/tests/extension/utils.test.ts
index 50da2031..09306d7e 100644
--- a/tests/extension/utils.test.ts
+++ b/tests/extension/utils.test.ts
@@ -1,5 +1,6 @@
 import vscode, { commands, workspace } from 'vscode'
 import { expect, vi, test, beforeEach, beforeAll, afterAll, suite } from 'vitest'
+import { v4 } from 'uuid'
 
 import {
   getTerminalByCell,
@@ -21,28 +22,39 @@ import { CellAnnotations } from '../../src/types'
 vi.mock('../../src/extension/grpc/client', () => ({}))
 vi.mock('../../src/extension/grpc/runnerTypes', () => ({}))
 
-vi.mock('vscode', () => ({
-  default: {
-    window: {
-      terminals: [
-        { creationOptions: { env: {} } },
-        { creationOptions: { env: { RUNME_ID: 'foobar:123' } } }
-      ]
+vi.mock('vscode', async () => {
+  const { v4 } = await vi.importActual('uuid') as typeof import('uuid')
+
+  const uuid1 = v4()
+  const uuid2 = v4()
+
+  return ({
+    default: {
+      window: {
+        terminals: [
+          { creationOptions: { env: { RUNME_ID: uuid1 } }, name: `echo hello (RUNME_ID: ${uuid1})` },
+          { creationOptions: { env: { RUNME_ID: uuid2 } }, name: `echo hi (RUNME_ID: ${uuid2})` }
+        ]
+      },
+      workspace: {
+        getConfiguration: vi.fn()
+      },
+      env: {
+        machineId: 'test-machine-id'
+      },
+      NotebookCellKind: {
+        Markup: 1,
+        Code: 2,
+      }
     },
     workspace: {
       getConfiguration: vi.fn()
     },
-    env: {
-      machineId: 'test-machine-id'
+    commands: {
+      executeCommand: vi.fn()
     },
-  },
-  workspace: {
-    getConfiguration: vi.fn()
-  },
-  commands: {
-    executeCommand: vi.fn()
-  },
-}))
+  })
+})
 vi.mock('vscode-telemetry')
 
 const PATH = process.env.PATH
@@ -53,22 +65,23 @@ beforeAll(() => {
 afterAll(() => { process.env.PATH = PATH })
 
 test('isInteractive', () => {
-  // when set to false in configutaration
-  vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({ get: vi.fn().mockReturnValue(false) } as any)
-  expect(getAnnotations({ metadata: {} } as any).interactive).toBe(false)
-  expect(getAnnotations({ metadata: {} } as any).interactive).toBe(false)
   expect(getAnnotations({ metadata: { interactive: 'true' } } as any).interactive).toBe(true)
-
-  vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({ get: vi.fn().mockReturnValue(true) } as any)
-  expect(getAnnotations({ metadata: {} } as any).interactive).toBe(true)
-  expect(getAnnotations({ metadata: {} } as any).interactive).toBe(true)
+  expect(getAnnotations({ metadata: { interactive: 'false' } } as any).interactive).toBe(false)
+  expect(getAnnotations({ metadata: { } } as any).interactive).toBe(true)
 })
 
 test('getTerminalByCell', () => {
-  expect(getTerminalByCell({ document: { fileName: 'foo' }, index: 42} as any))
-    .toBe(undefined)
-  expect(getTerminalByCell({ document: { fileName: 'foobar' }, index: 123} as any))
-    .not.toBe(undefined)
+  expect(getTerminalByCell({
+    metadata: { 'runme.dev/uuid': vscode.window.terminals[0].creationOptions['env'].RUNME_ID },
+    kind: 2,
+  } as any))
+    .toBeTruthy()
+
+    expect(getTerminalByCell({
+      metadata: { 'runme.dev/uuid': v4() },
+      kind: 2,
+    } as any))
+      .toBeUndefined()
 })
 
 test('resetEnv', () => {
@@ -264,8 +277,8 @@ suite('#getAnnotations', () => {
     expect(d).toStrictEqual(
       <CellAnnotations>{
         background: false,
-        closeTerminalOnSuccess: false,
-        interactive: false,
+        closeTerminalOnSuccess: true,
+        interactive: true,
         mimeType: 'text/plain',
         name: 'command-123',
         'runme.dev/uuid': '48d86c43-84a4-469d-8c78-963513b0f9d0'
@@ -283,8 +296,8 @@ suite('#getAnnotations', () => {
 
     expect(getAnnotations(hello)).toStrictEqual({
       background: false,
-      closeTerminalOnSuccess: false,
-      interactive: false,
+      closeTerminalOnSuccess: true,
+      interactive: true,
       mimeType: 'text/plain',
       name: 'echo-hello',
     })