Skip to main content
Alpha

Add a task runtime adapter

createDefaultTaskExecutor({ adapters, customAdapters }) lets you keep the built-in task executor while adding a controlled runtime adapter. Use this when a task must run through a host runtime that NetScript does not ship by default.

Prerequisites

  • A TaskRuntimeAdapterLike implementation.
  • A task type string reserved for your adapter.
  • A clear sandbox story for the external runtime.
  • A test task that exercises stdout, stderr, timeout, and failure.

Start from the real adapter contract

The executor consumes TaskRuntimeAdapterLike: id, runtime, supports(task), and execute(task, options).

import type {
  ResolvedTaskExecutionOptions,
  TaskDefinition,
  TaskResult,
  TaskRuntimeAdapterLike,
} from '@netscript/plugin-workers-core/executor';

export const nodeAdapter: TaskRuntimeAdapterLike = {
  id: 'node-runtime-adapter',
  runtime: null,

  supports(task: TaskDefinition): boolean {
    return task.type === 'node';
  },

  async execute(
    task: TaskDefinition,
    options: ResolvedTaskExecutionOptions,
  ): Promise<TaskResult> {
    const startedAt = new Date().toISOString();
    const started = performance.now();
    const command = new Deno.Command('node', {
      args: [task.entrypoint ?? '', ...(options.args ?? [])],
      cwd: options.cwd || undefined,
      env: options.env,
      stdout: 'piped',
      stderr: 'piped',
      signal: options.signal,
    });

    const output = await command.output();
    const duration = Math.round(performance.now() - started);
    const stdout = new TextDecoder().decode(output.stdout);
    const stderr = new TextDecoder().decode(output.stderr);

    options.onStdout?.(stdout);
    options.onStderr?.(stderr);

    return {
      taskId: task.id,
      status: output.success ? 'completed' : 'failed',
      exitCode: output.code,
      stdout,
      stderr,
      duration,
      success: output.success,
      error: output.success ? null : stderr || `node exited ${output.code}`,
      result: null,
      startedAt,
      completedAt: new Date().toISOString(),
      attempt: 1,
    };
  },
};

Register the adapter

Use customAdapters when your task type is not one of the built-in TaskType values. The default adapter map still covers deno, python, dotnet, shell, powershell, cmd, and executable.

import { createDefaultTaskExecutor } from '@netscript/plugin-workers-core/executor';
import { nodeAdapter } from './node-adapter.ts';

const executor = createDefaultTaskExecutor({
  customAdapters: {
    node: nodeAdapter,
  },
  defaults: {
    cwd: Deno.cwd(),
    timeout: 300_000,
  },
});

const result = await executor.execute(
  {
    id: 'render-invoice',
    type: 'node',
    entrypoint: './tasks/render-invoice.mjs',
    args: ['--invoice', 'inv_123'],
  },
  {
    correlationId: 'invoice.inv_123',
    onStdout: (line) => console.log(line),
    onStderr: (line) => console.error(line),
  },
);

if (!result.success) {
  throw new Error(result.error ?? 'task failed');
}

Failure modes

  • supports(task) returns false: the executor returns a failed TaskResult for unsupported runtimes.
  • The host binary is missing: return a failed TaskResult with stderr or an error message.
  • Timeout handling is adapter-owned. Respect options.timeout and options.signal in your adapter.
  • Permission sandboxing is Deno-specific. Python, .NET, shell, PowerShell, cmd, executable, and custom adapters inherit the worker process OS permissions unless your adapter adds its own sandbox.

Next steps