Skip to content

Branch remote runner plugin fixe#46

Draft
proto-aiken-13 wants to merge 26 commits into
source-academy:mainfrom
proto-aiken-13:branch-remoteRunnerPlugin-fixed
Draft

Branch remote runner plugin fixe#46
proto-aiken-13 wants to merge 26 commits into
source-academy:mainfrom
proto-aiken-13:branch-remoteRunnerPlugin-fixed

Conversation

@proto-aiken-13

Copy link
Copy Markdown
Contributor

plugins repo PR description
Title:
feat(ev3): add conductor-based EV3 evaluator bundle
Description:

Overview

Adds a conductor-compatible EV3 evaluator to the remoteExecution runner
package as part of the py-slang EV3 integration. This replaces the
js-slang compilation step in the existing EV3 remote execution flow
with py-slang's EV3Engine, which compiles Python code to SVML.

proto-aiken-13 and others added 25 commits June 22, 2026 23:05
Set up the remote runner plugin, and associated directory and index files to be linked to the ev3 engine in py-slang
Updated the directory of the remote runner plugin
Modified the remoteRunnerPlugin repository structure and added the ev3Engine usage with the relevant test suite.

The updated directory structure is as follows:

plugins/
├── src/
│   ├── common/
│   │   └── test/
│   │       └── src/
│   │           └── index.ts          # Added PySlangMessage type
│   ├── runner/
│   │   ├── remoteExecution/          # New package
│   │   │   ├── src/
│   │   │   │   └── index.ts          # Entry point, exports remoteRunnerPlugin
│   │   │   ├── index.ts              # Abstract remoteRunnerPlugin class
│   │   │   ├── jest.config.cjs
│   │   │   ├── manifest.json
│   │   │   ├── package.json
│   │   │   ├── rollup.config.mjs
│   │   │   └── tsconfig.json
│   │   └── test/
│   │       └── src/
│   │           ├── __tests__/
│   │           │   └── runner.test.ts  # Tests engine-plugin connection
│   │           └── index.ts
└── lib/
    └── build.ts                      # Fixed Windows yarn spawn issue
…onfigs

- Replace jest/ts-jest/@types/jest with vitest in remoteExecution package
- Add proper installable plugin fields (files, exports, publishConfig, peerDeps)
- Remove spurious `abstract` from remoteRunnerPlugin (has no abstract methods)
- Add `type` modifier to interface imports in remoteExecution/index.ts
- Fix both rollup configs to use typescript() without __dirname (not valid in ESM)
- Remove hardcoded Windows path from remoteExecution/tsconfig.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…/wasm-util

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move @sourceacademy/wasm-util before @sourceacademy/web-* (alphabetical)
- Merge commander@^2.19.0 range (nearley dep)
- Add fast-levenshtein@^3.0.0 and fastest-levenshtein@^1.0.7 (py-slang dep)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
py-slang is imported from source (py-slang/src/...) and its internal
imports don't use type-only syntax, which violates the repo's
verbatimModuleSyntax:true setting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lity

py-slang/src uses BigInt (requires ES2020+) and imports untyped modules
(moo, nearley, fast-levenshtein). Both remoteExecution and runner-test
pull in py-slang source transitively, so both need these overrides.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Importing from py-slang/src/ pulls in untyped modules (moo, nearley)
and namespace patterns that don't work under our strict tsconfig.
noCheck:true compiles without type-checking, which is appropriate
until py-slang exposes EV3Engine through its public API with proper types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
runner-test/src/index.ts re-exported remoteRunnerPlugin via a relative
path into another package's source, which rollup can't resolve when
building the bundle. The test file imports it directly from the package
so the re-export is redundant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change inline type qualifiers to top-level import type (no-import-type-side-effects)
- Add eslint-disable comments for required `as any` mock casts in tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without .ts in the extensions list, @rollup/plugin-node-resolve could not
find py-slang/src/engines/ev3/EV3Engine.ts and left it as an unresolved
external. This caused the bundle to emit a bare require() pointing at the
TypeScript source, which fails at test time with a SyntaxError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rollup/plugin-typescript only transforms files that are part of the TS
program (files in tsconfig include). Without this, when nodeResolve
finds EV3Engine.ts from node_modules, rollup's default acorn parser
receives the TypeScript file and fails. Adding py-slang to include
lets the TypeScript compiler process and transpile it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pileModule

@rollup/plugin-typescript excludes node_modules from its TypeScript program,
so py-slang's TypeScript source files (EV3Engine.ts, etc.) never get
transformed — rollup's acorn parser receives raw TypeScript and fails.

Add a lightweight inline plugin that calls ts.transpileModule() on any
.ts file from node_modules before the main TypeScript plugin runs. This
strips TypeScript syntax without needing a full type-checking program.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
py-slang depends on moo and nearley, which are CommonJS UMD modules.
When rollup bundles them as ESM it can't resolve their default exports
without the commonjs interop plugin. Adding commonjs() before our
TypeScript transform plugin resolves the 'default is not exported' error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntry

Yarn lockfile workspace entry must mirror package.json; adding the dep
to package.json without updating yarn.lock triggers YN0028.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bundling py-slang TypeScript source causes CJS interop and circular
dependency issues at runtime. Since py-slang is a listed dependency
(installed alongside the plugin), it's correct to keep it external.

At test time, vitest's server.deps.inline transforms py-slang with
esbuild, handling TypeScript source and CJS interop cleanly without
the issues rollup's commonjs plugin introduces.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The pre-built dist/index.mjs has py-slang as an external import, and
Node.js can't load TypeScript source files natively. Using vi.mock to
stub EV3Engine tests the plugin's channel subscription behavior without
needing to load py-slang's TypeScript at test time. Also fixes the
debug FORCE_FAIL assertion and adds a meaningful result assertion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt rule

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new conductor-compatible EV3 evaluator to the remoteExecution
runner package:

- Ev3Evaluator.ts: BasicEvaluator subclass that calls EV3Engine from
  py-slang to compile Python code to SVML, returning the result via
  conductor's sendResult channel back to the frontend host
- entry.ts: bundle entry point that initialises the Ev3Evaluator via
  conductor's initialise utility
- rollup.config.mjs: updated to include a second build target that
  bundles entry.ts into a self-contained iife output (ev3-pyslang.js)
  for use as a Web Worker in the frontend
@changeset-bot

changeset-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 764ee5b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

💥 An error occurred when fetching the changed packages and changesets in this PR
Some errors occurred when validating the changesets config:
The package or glob expression "@sourceacademy/web-stepper" is specified in the `ignore` option but it is not found in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new remote execution runner package (@sourceacademy/runner-remote-execution) integrating with the py-slang EV3 engine, along with corresponding Rollup and TypeScript configurations, and updates the test suite to verify its behavior. The review feedback identifies critical issues including a missing entry file (src/entry.ts) in the Rollup configuration, an invalid noCheck compiler option in tsconfig.json, and potential unhandled promise rejections in the asynchronous subscription callback. Additionally, the reviewer suggests renaming remoteRunnerPlugin to RemoteRunnerPlugin to follow PascalCase conventions, and improving type safety in the constructor to avoid using any.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +16 to +22
this.__channel.subscribe(async message => {
if (message.type === "run") {
const result = await this.engine.execute(message.code);
console.log("Engine response:", result);
this.__channel.send({ type: "result", output: JSON.stringify(result) });
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If this.engine.execute or JSON.stringify throws an error, the asynchronous subscription callback will result in an unhandled promise rejection. Wrapping the execution in a try-catch block allows us to handle the error gracefully and send an error message back through the channel using the error message type defined in PySlangMessage.

    this.__channel.subscribe(async message => {
      if (message.type === "run") {
        try {
          const result = await this.engine.execute(message.code);
          console.log("Engine response:", result);
          this.__channel.send({ type: "result", output: JSON.stringify(result) });
        } catch (error) {
          console.error("Engine execution failed:", error);
          this.__channel.send({
            type: "error",
            message: error instanceof Error ? error.message : String(error),
          });
        }
      }
    });

},
// new EV3 conductor worker bundle
{
input: "src/entry.ts",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The file src/entry.ts specified as the input for the EV3 conductor worker bundle does not exist in the repository. This will cause the Rollup build to fail. Please correct the input path or add the missing src/entry.ts file.

import type { IPlugin, IChannel, IConduit } from "@sourceacademy/conductor/conduit";
import { EV3Engine } from "py-slang/src/engines/ev3/EV3Engine";

export class remoteRunnerPlugin implements IPlugin {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Class names in TypeScript should follow the PascalCase naming convention (e.g., as per standard TypeScript style guides). Renaming remoteRunnerPlugin to RemoteRunnerPlugin improves readability and consistency with standard naming conventions.

Suggested change
export class remoteRunnerPlugin implements IPlugin {
export class RemoteRunnerPlugin implements IPlugin {

Comment on lines +11 to +12
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(_conduit: IConduit, [channel]: IChannel<any>[]) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of using any and disabling the ESLint rule, we can type the channel parameter directly as IChannel<PySlangMessage>[]. This preserves type safety and removes the need for the ESLint disable comment.

  constructor(_conduit: IConduit, [channel]: IChannel<PySlangMessage>[]) {

"declaration": true,
"outDir": "./dist",
"rootDir": ".",
"noCheck": true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

noCheck is not a valid TypeScript compiler option in tsconfig.json. If the intention is to skip type checking of declaration files, use skipLibCheck: true. If the intention is to disable type checking entirely, it is highly discouraged as it defeats the purpose of using TypeScript.

Suggested change
"noCheck": true
"skipLibCheck": true

@@ -0,0 +1 @@
export { remoteRunnerPlugin } from "../index";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the export to use the PascalCase class name RemoteRunnerPlugin.

Suggested change
export { remoteRunnerPlugin } from "../index";
export { RemoteRunnerPlugin } from "../index";

},
}));

import { remoteRunnerPlugin } from "@sourceacademy/runner-remote-execution";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the import to use the PascalCase class name RemoteRunnerPlugin.

Suggested change
import { remoteRunnerPlugin } from "@sourceacademy/runner-remote-execution";
import { RemoteRunnerPlugin } from "@sourceacademy/runner-remote-execution";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockConduit = {} as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new remoteRunnerPlugin(mockConduit, [mockChannel as any]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the instantiation to use the PascalCase class name RemoteRunnerPlugin.

Suggested change
new remoteRunnerPlugin(mockConduit, [mockChannel as any]);
new RemoteRunnerPlugin(mockConduit, [mockChannel as any]);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants