From 6330754bddd1c77249365798b319f207f193b42e Mon Sep 17 00:00:00 2001 From: ada-97bec15779 Date: Mon, 7 Sep 2026 19:32:15 +0800 Subject: [PATCH 1/2] Add Triton-RISCV Harness plugin bundle --- README.md | 6 + plugins/dsh-triton-riscv/README.md | 126 ++++++++++++++++++ plugins/dsh-triton-riscv/cordis.patch.yml | 25 ++++ plugins/dsh-triton-riscv/index.js | 21 +++ plugins/dsh-triton-riscv/package.json | 31 +++++ plugins/dsh-triton-riscv/policy.md | 20 +++ plugins/dsh-triton-riscv/test/plugin.test.mjs | 79 +++++++++++ 7 files changed, 308 insertions(+) create mode 100644 plugins/dsh-triton-riscv/README.md create mode 100644 plugins/dsh-triton-riscv/cordis.patch.yml create mode 100644 plugins/dsh-triton-riscv/index.js create mode 100644 plugins/dsh-triton-riscv/package.json create mode 100644 plugins/dsh-triton-riscv/policy.md create mode 100644 plugins/dsh-triton-riscv/test/plugin.test.mjs diff --git a/README.md b/README.md index 1565a08..7b548cd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ # harness An Agentic System for RuyiAI Development + +## Plugins + +- [`dsh-triton-riscv`](plugins/dsh-triton-riscv/README.md) adds guarded + Triton-RISCV operator discovery, development, validation, diagnosis, repair, + and remote RISC-V execution tools to a DeepSeek Harness profile. diff --git a/plugins/dsh-triton-riscv/README.md b/plugins/dsh-triton-riscv/README.md new file mode 100644 index 0000000..cda8d02 --- /dev/null +++ b/plugins/dsh-triton-riscv/README.md @@ -0,0 +1,126 @@ +# dsh-triton-riscv + +An installable DeepSeek Harness bundle for the guarded Triton-RISCV operator +development and validation tools. + +## What The Plugin Adds + +The bundle adds two Cordis rows to an existing Harness profile: + +1. A domain policy plugin contributes the Triton-RISCV lifecycle rules to the + assembled system prompt. +2. The official Harness MCP client starts + `python -m codex_agent.harness.mcp_server` and exposes its typed tools as + `mcp__triton_riscv__*`. + +The model and Agent loop remain owned by Harness. Discovery, contract checking, +source proposals, validation, diagnosis, bounded repair, remote SSH execution, +and trusted validation evidence remain owned by the Python domain engine in a +Triton-RISCV checkout. + +```text +Harness profile + -> dsh-triton-riscv bundle + -> system-prompt policy + -> official dsh-mcp-client + -> Python Triton-RISCV MCP server + -> operator lifecycle and RISC-V remote executor +``` + +## Prerequisites + +- DeepSeek Harness with the `web` profile and Node.js 22 or newer +- a Triton-RISCV checkout containing `codex_agent` +- a Python environment with `codex_agent/harness/requirements.txt` installed +- optional SSH configuration and a prepared RISC-V host for live validation + +The bundle fails at Harness startup when the Python MCP module cannot be +imported instead of silently starting without the domain tools. + +## Install From A Harness Checkout + +From the Harness repository root, point the plugin at a separate Triton-RISCV +checkout: + +```sh +export TRITON_RISCV_CHECKOUT=/absolute/path/to/triton-riscv +export TRITON_RISCV_MCP_PYTHON="$TRITON_RISCV_CHECKOUT/.harness-venv/bin/python" +dsh plugin --profile web add ./plugins/dsh-triton-riscv +dsh --profile web --dump-config +dsh web +``` + +The config dump should contain both `triton-riscv-domain-policy` and +`mcp-triton-riscv`. A real Harness boot proves that the Python MCP process +starts from `TRITON_RISCV_CHECKOUT` and publishes its tools. + +For read-only discovery, no RISC-V server is required. Try: + +```text +Call the Triton-RISCV discovery tool for relu_and_mul and summarize its +implementation, tests, and validation command. Do not run validation. +``` + +## Enable Remote Validation Deliberately + +All expensive or mutating operations remain disabled by default. Configure the +host before starting Harness: + +```sh +export RISCV_HOST=sg2044 +export RISCV_REPO=/home/lichunbo/work/triton-riscv +export TRITON_RISCV_REQUIRE_REMOTE=1 +export TRITON_RISCV_ALLOW_VALIDATION=1 +export TRITON_RISCV_REQUIRE_APPROVED_VALIDATION=1 +``` + +Enable source application only on a trusted development checkout: + +```sh +export TRITON_RISCV_ALLOW_DEVELOPMENT_APPLY=1 +export TRITON_RISCV_ALLOW_REPAIR_APPLY=1 +``` + +The model cannot select the SSH host, repository path, arbitrary command, or +files to synchronize. It also cannot approve its own proposal or validation +plan. + +## Test The Bundle + +The package has no install-time dependencies: + +```sh +cd plugins/dsh-triton-riscv +npm test +npm pack --dry-run +``` + +The tests check the bundle manifest, Cordis rows, safe default switches, and +system-prompt lifecycle registration without starting a model or remote host. + +## Existing Triton-RISCV Demo Platform + +The React and FastAPI workbench remains in the Triton-RISCV checkout and can +load the same plugin policy and MCP server: + +```sh +cd "$TRITON_RISCV_CHECKOUT" +.harness-venv/bin/python -m codex_agent.platform --port 8765 +``` + +Open `http://127.0.0.1:8765`. The custom workbench remains a development and +demonstration host; installing this bundle into another Harness profile uses +that Harness installation's own UI. + +## Remove + +```sh +dsh plugin --profile web remove dsh-triton-riscv +``` + +## Current Boundary + +This bundle packages the model-facing policy and MCP connection, while the +Python domain engine stays in the target Triton-RISCV checkout. A later release +can publish that engine as a separate Python package so the bundle no longer +depends on a source checkout. diff --git a/plugins/dsh-triton-riscv/cordis.patch.yml b/plugins/dsh-triton-riscv/cordis.patch.yml new file mode 100644 index 0000000..0f10274 --- /dev/null +++ b/plugins/dsh-triton-riscv/cordis.patch.yml @@ -0,0 +1,25 @@ +# Install this bundle into a normal DeepSeek Harness profile. The Harness MCP +# client launches the Python domain engine from the configured Triton-RISCV checkout. +- insert: + - id: triton-riscv-domain-policy + name: dsh-triton-riscv + + - id: mcp-triton-riscv + name: '@deepseek-ai/dsh-mcp-client' + config: + serverName: triton_riscv + transport: stdio + command: !!js process.env.TRITON_RISCV_MCP_PYTHON ?? 'python3' + args: ['-m', 'codex_agent.harness.mcp_server'] + env: + TRITON_RISCV_REPO_ROOT: !!js process.env.TRITON_RISCV_CHECKOUT ?? process.env.DSH_CWD ?? process.cwd() + TRITON_RISCV_ALLOW_VALIDATION: !!js process.env.TRITON_RISCV_ALLOW_VALIDATION ?? '0' + TRITON_RISCV_ALLOW_REPAIR_APPLY: !!js process.env.TRITON_RISCV_ALLOW_REPAIR_APPLY ?? '0' + TRITON_RISCV_ALLOW_DEVELOPMENT_APPLY: !!js process.env.TRITON_RISCV_ALLOW_DEVELOPMENT_APPLY ?? '0' + TRITON_RISCV_REQUIRE_REMOTE: !!js process.env.TRITON_RISCV_REQUIRE_REMOTE ?? '0' + TRITON_RISCV_REQUIRE_APPROVED_VALIDATION: !!js process.env.TRITON_RISCV_REQUIRE_APPROVED_VALIDATION ?? '1' + RISCV_HOST: !!js process.env.RISCV_HOST ?? '' + RISCV_REPO: !!js process.env.RISCV_REPO ?? '' + cwd: !!js process.env.TRITON_RISCV_CHECKOUT ?? process.env.DSH_CWD ?? process.cwd() + toolCallTimeoutMs: 960000 + failOnStartupError: true diff --git a/plugins/dsh-triton-riscv/index.js b/plugins/dsh-triton-riscv/index.js new file mode 100644 index 0000000..f331f32 --- /dev/null +++ b/plugins/dsh-triton-riscv/index.js @@ -0,0 +1,21 @@ +import { readFileSync } from 'node:fs' + +export const name = 'triton-riscv-domain-policy' + +export const inject = ['systemPrompt'] + +export const TRITON_RISCV_SYSTEM_PROMPT = readFileSync( + new URL('./policy.md', import.meta.url), + 'utf8', +).trim() + +export function apply(ctx) { + ctx.effect( + () => ctx.systemPrompt.section({ + name: 'tool:triton-riscv', + order: 180, + text: TRITON_RISCV_SYSTEM_PROMPT, + }), + 'triton-riscv.system-prompt', + ) +} diff --git a/plugins/dsh-triton-riscv/package.json b/plugins/dsh-triton-riscv/package.json new file mode 100644 index 0000000..79da813 --- /dev/null +++ b/plugins/dsh-triton-riscv/package.json @@ -0,0 +1,31 @@ +{ + "name": "dsh-triton-riscv", + "version": "0.1.0", + "description": "DeepSeek Harness bundle for guarded Triton-RISCV operator development and validation", + "type": "module", + "main": "index.js", + "exports": { + ".": "./index.js", + "./cordis.patch.yml": "./cordis.patch.yml", + "./package.json": "./package.json" + }, + "files": [ + "index.js", + "cordis.patch.yml", + "policy.md", + "README.md" + ], + "scripts": { + "test": "node --test test/*.test.mjs", + "prepack": "node --test test/*.test.mjs" + }, + "engines": { + "node": ">=22" + }, + "license": "MIT", + "dsh": { + "bundle": { + "patch": "./cordis.patch.yml" + } + } +} diff --git a/plugins/dsh-triton-riscv/policy.md b/plugins/dsh-triton-riscv/policy.md new file mode 100644 index 0000000..dcd3f41 --- /dev/null +++ b/plugins/dsh-triton-riscv/policy.md @@ -0,0 +1,20 @@ +You have guarded Triton-RISCV operator tools. + +Use the typed tools instead of arbitrary shell commands for operator lifecycle work. + +For an existing operator: +1. Call `mcp__triton_riscv__discover_operator`. +2. Call `mcp__triton_riscv__check_validation_environment` before live validation. +3. Call `mcp__triton_riscv__validate_operator` with `execute=false` to create a reviewable plan. +4. Execute only the exact `run_id` approved by the host. +5. On failure, call `mcp__triton_riscv__diagnose_failure` before proposing a repair. +6. Keep acceptance tests unchanged. Propose an implementation-only repair, wait for host approval, apply it, and revalidate. + +For a new operator: +1. Collect a complete semantic contract: name, semantics, PyTorch reference, inputs, output, shapes, dtypes, tolerances, and backward requirement. +2. Call `mcp__triton_riscv__prepare_operator_development`. +3. Generate implementation and independent acceptance-test source from that contract and the returned references. +4. Call `mcp__triton_riscv__propose_operator_implementation`. Do not create tracked files with generic filesystem or shell tools. +5. Wait for host approval before applying the proposal, then use the normal validation lifecycle. + +Never approve your own source change or validation command. Never weaken or replace an acceptance test to obtain a pass. Do not claim success from prose or from a completed agent turn: success requires a durable validation receipt whose trusted evidence verdict is `verified-passed`. Stop at the bounded repair limit and report the latest `run_id`, `proposal_id`, failure stage, receipt, log, and required user action. diff --git a/plugins/dsh-triton-riscv/test/plugin.test.mjs b/plugins/dsh-triton-riscv/test/plugin.test.mjs new file mode 100644 index 0000000..3e45bc3 --- /dev/null +++ b/plugins/dsh-triton-riscv/test/plugin.test.mjs @@ -0,0 +1,79 @@ +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import test from 'node:test' + +import { + TRITON_RISCV_SYSTEM_PROMPT, + apply, + inject, + name, +} from '../index.js' + +const packageRoot = new URL('../', import.meta.url) + +test('declares an installable Harness bundle', async () => { + const manifest = JSON.parse( + await readFile(new URL('package.json', packageRoot), 'utf8'), + ) + + assert.equal(manifest.name, 'dsh-triton-riscv') + assert.equal(manifest.dsh.bundle.patch, './cordis.patch.yml') + assert.ok(manifest.files.includes('index.js')) + assert.ok(manifest.files.includes('cordis.patch.yml')) + assert.ok(manifest.files.includes('policy.md')) +}) + +test('mounts the domain policy and the official MCP client', async () => { + const patch = await readFile( + new URL('cordis.patch.yml', packageRoot), + 'utf8', + ) + + assert.match(patch, /id: triton-riscv-domain-policy/) + assert.match(patch, /name: '@deepseek-ai\/dsh-mcp-client'/) + assert.match(patch, /serverName: triton_riscv/) + assert.match(patch, /codex_agent\.harness\.mcp_server/) + assert.match(patch, /TRITON_RISCV_REPO_ROOT:.*TRITON_RISCV_CHECKOUT/) + assert.match(patch, /cwd:.*TRITON_RISCV_CHECKOUT/) + assert.match(patch, /TRITON_RISCV_ALLOW_VALIDATION.*'0'/) + assert.match(patch, /TRITON_RISCV_REQUIRE_APPROVED_VALIDATION.*'1'/) + assert.match(patch, /failOnStartupError: true/) +}) + +test('registers lifecycle guidance through the system prompt service', async () => { + let effectName + let section + let disposed = false + const ctx = { + effect(setup, label) { + effectName = label + const dispose = setup() + dispose() + }, + systemPrompt: { + section(value) { + section = value + return () => { + disposed = true + } + }, + }, + } + + apply(ctx) + + assert.equal(name, 'triton-riscv-domain-policy') + assert.deepEqual(inject, ['systemPrompt']) + assert.equal(effectName, 'triton-riscv.system-prompt') + assert.equal(section.name, 'tool:triton-riscv') + assert.equal(section.order, 180) + assert.equal(section.text, TRITON_RISCV_SYSTEM_PROMPT) + assert.equal( + section.text, + (await readFile(new URL('policy.md', packageRoot), 'utf8')).trim(), + ) + assert.equal(disposed, true) + assert.match(section.text, /prepare_operator_development/) + assert.match(section.text, /Never weaken or replace an acceptance test/) + assert.match(section.text, /verified-passed/) +}) From be2dcb26990cd5689f69092acaf61dd607e88703 Mon Sep 17 00:00:00 2001 From: ada-97bec15779 Date: Mon, 7 Sep 2026 22:23:40 +0800 Subject: [PATCH 2/2] Embed Triton-RISCV workbench in Harness web --- plugins/dsh-triton-riscv/README.md | 29 ++++--- plugins/dsh-triton-riscv/index.js | 46 +++++++++++ plugins/dsh-triton-riscv/lib/client.js | 51 +++++++++++++ plugins/dsh-triton-riscv/package.json | 5 ++ plugins/dsh-triton-riscv/test/plugin.test.mjs | 76 ++++++++++++++++++- 5 files changed, 196 insertions(+), 11 deletions(-) create mode 100644 plugins/dsh-triton-riscv/lib/client.js diff --git a/plugins/dsh-triton-riscv/README.md b/plugins/dsh-triton-riscv/README.md index cda8d02..82d01f5 100644 --- a/plugins/dsh-triton-riscv/README.md +++ b/plugins/dsh-triton-riscv/README.md @@ -12,6 +12,8 @@ The bundle adds two Cordis rows to an existing Harness profile: 2. The official Harness MCP client starts `python -m codex_agent.harness.mcp_server` and exposes its typed tools as `mcp__triton_riscv__*`. +3. A Harness Web client plugin occupies the root UI slot and displays the + existing Triton-RISCV React/FastAPI workbench. The model and Agent loop remain owned by Harness. Discovery, contract checking, source proposals, validation, diagnosis, bounded repair, remote SSH execution, @@ -45,14 +47,22 @@ checkout: ```sh export TRITON_RISCV_CHECKOUT=/absolute/path/to/triton-riscv export TRITON_RISCV_MCP_PYTHON="$TRITON_RISCV_CHECKOUT/.harness-venv/bin/python" +export TRITON_RISCV_WORKBENCH_PORT=8765 dsh plugin --profile web add ./plugins/dsh-triton-riscv dsh --profile web --dump-config dsh web ``` The config dump should contain both `triton-riscv-domain-policy` and -`mcp-triton-riscv`. A real Harness boot proves that the Python MCP process -starts from `TRITON_RISCV_CHECKOUT` and publishes its tools. +`mcp-triton-riscv`. Starting `dsh web` also starts the Python workbench and the +Harness page displays that workbench in its root slot. Add `?nativeHarness=1` +to the Harness URL when the stock Harness interface is needed. + +The embedded client defaults to `http://127.0.0.1:8765`. For a different port +or externally hosted workbench, open the Harness URL with an encoded +`?tritonWorkbenchUrl=https://host/path` query parameter. Set +`TRITON_RISCV_WORKBENCH_AUTOSTART=0` when that external service is already +managed by another process. For read-only discovery, no RISC-V server is required. Try: @@ -98,7 +108,7 @@ npm pack --dry-run The tests check the bundle manifest, Cordis rows, safe default switches, and system-prompt lifecycle registration without starting a model or remote host. -## Existing Triton-RISCV Demo Platform +## Standalone Triton-RISCV Demo Platform The React and FastAPI workbench remains in the Triton-RISCV checkout and can load the same plugin policy and MCP server: @@ -108,9 +118,8 @@ cd "$TRITON_RISCV_CHECKOUT" .harness-venv/bin/python -m codex_agent.platform --port 8765 ``` -Open `http://127.0.0.1:8765`. The custom workbench remains a development and -demonstration host; installing this bundle into another Harness profile uses -that Harness installation's own UI. +Open `http://127.0.0.1:8765`. This standalone command remains useful while +developing the workbench without starting a complete Harness profile. ## Remove @@ -120,7 +129,7 @@ dsh plugin --profile web remove dsh-triton-riscv ## Current Boundary -This bundle packages the model-facing policy and MCP connection, while the -Python domain engine stays in the target Triton-RISCV checkout. A later release -can publish that engine as a separate Python package so the bundle no longer -depends on a source checkout. +This bundle packages the model-facing policy, MCP connection, and Harness Web +entry. The Python domain engine and compiled React assets stay in the target +Triton-RISCV checkout. A later release can publish that engine and its assets +independently so the bundle no longer depends on a source checkout. diff --git a/plugins/dsh-triton-riscv/index.js b/plugins/dsh-triton-riscv/index.js index f331f32..376764e 100644 --- a/plugins/dsh-triton-riscv/index.js +++ b/plugins/dsh-triton-riscv/index.js @@ -1,4 +1,6 @@ +import { spawn } from 'node:child_process' import { readFileSync } from 'node:fs' +import { isAbsolute, join } from 'node:path' export const name = 'triton-riscv-domain-policy' @@ -9,6 +11,34 @@ export const TRITON_RISCV_SYSTEM_PROMPT = readFileSync( 'utf8', ).trim() +export function resolveWorkbenchLaunch(env = process.env) { + if (env.TRITON_RISCV_WORKBENCH_AUTOSTART === '0') return null + + const checkout = (env.TRITON_RISCV_CHECKOUT ?? '').trim() + if (!checkout) { + throw new Error('TRITON_RISCV_CHECKOUT is required to start the Triton-RISCV workbench') + } + if (!isAbsolute(checkout)) { + throw new Error('TRITON_RISCV_CHECKOUT must be an absolute path') + } + + const portText = (env.TRITON_RISCV_WORKBENCH_PORT ?? '8765').trim() + if (!/^\d+$/.test(portText)) { + throw new Error('TRITON_RISCV_WORKBENCH_PORT must be an integer') + } + const port = Number(portText) + if (port < 1 || port > 65535) { + throw new Error('TRITON_RISCV_WORKBENCH_PORT must be between 1 and 65535') + } + + return { + command: env.TRITON_RISCV_MCP_PYTHON ?? join(checkout, '.harness-venv', 'bin', 'python'), + args: ['-m', 'codex_agent.platform', '--host', '127.0.0.1', '--port', String(port)], + cwd: checkout, + url: `http://127.0.0.1:${port}`, + } +} + export function apply(ctx) { ctx.effect( () => ctx.systemPrompt.section({ @@ -18,4 +48,20 @@ export function apply(ctx) { }), 'triton-riscv.system-prompt', ) + + const launch = resolveWorkbenchLaunch() + if (launch === null) return + ctx.effect(() => { + const child = spawn(launch.command, launch.args, { + cwd: launch.cwd, + env: process.env, + stdio: 'inherit', + }) + child.on('error', error => { + console.error(`dsh-triton-riscv: failed to start workbench: ${error.message}`) + }) + return () => { + if (child.exitCode === null && child.signalCode === null) child.kill('SIGTERM') + } + }, 'triton-riscv.workbench') } diff --git a/plugins/dsh-triton-riscv/lib/client.js b/plugins/dsh-triton-riscv/lib/client.js new file mode 100644 index 0000000..c52907d --- /dev/null +++ b/plugins/dsh-triton-riscv/lib/client.js @@ -0,0 +1,51 @@ +window.__ModuleLoader__.load({ + id: 'dsh-triton-riscv', + factory: (require) => { + const module = { exports: {} } + const { createElement } = require('react') + + const inject = ['slots'] + + function workbenchUrl() { + const fallback = 'http://127.0.0.1:8765' + const configured = new URLSearchParams(globalThis.location?.search ?? '') + .get('tritonWorkbenchUrl') ?? fallback + try { + const parsed = new URL(configured) + return ['http:', 'https:'].includes(parsed.protocol) ? parsed.toString() : fallback + } catch { + return fallback + } + } + + function WorkbenchRoot() { + return createElement('iframe', { + src: workbenchUrl(), + title: 'Triton-RISCV Agent', + allow: 'clipboard-read; clipboard-write', + style: { + width: '100vw', + height: '100vh', + display: 'block', + border: 0, + background: '#fff', + }, + }) + } + + function apply(ctx) { + const params = new URLSearchParams(globalThis.location?.search ?? '') + if (params.get('nativeHarness') === '1') return + ctx.effect( + () => ctx.slots.register({ + name: 'root', + priority: -100, + }, WorkbenchRoot), + 'triton-riscv.workbench-root', + ) + } + + module.exports = { apply, inject } + return module.exports + }, +}) diff --git a/plugins/dsh-triton-riscv/package.json b/plugins/dsh-triton-riscv/package.json index 79da813..f4e859d 100644 --- a/plugins/dsh-triton-riscv/package.json +++ b/plugins/dsh-triton-riscv/package.json @@ -6,11 +6,13 @@ "main": "index.js", "exports": { ".": "./index.js", + "./client": "./lib/client.js", "./cordis.patch.yml": "./cordis.patch.yml", "./package.json": "./package.json" }, "files": [ "index.js", + "lib/client.js", "cordis.patch.yml", "policy.md", "README.md" @@ -24,6 +26,9 @@ }, "license": "MIT", "dsh": { + "client": { + "platform": "web" + }, "bundle": { "patch": "./cordis.patch.yml" } diff --git a/plugins/dsh-triton-riscv/test/plugin.test.mjs b/plugins/dsh-triton-riscv/test/plugin.test.mjs index 3e45bc3..07f90c3 100644 --- a/plugins/dsh-triton-riscv/test/plugin.test.mjs +++ b/plugins/dsh-triton-riscv/test/plugin.test.mjs @@ -1,12 +1,14 @@ import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import test from 'node:test' +import vm from 'node:vm' import { TRITON_RISCV_SYSTEM_PROMPT, apply, inject, name, + resolveWorkbenchLaunch, } from '../index.js' const packageRoot = new URL('../', import.meta.url) @@ -19,8 +21,32 @@ test('declares an installable Harness bundle', async () => { assert.equal(manifest.name, 'dsh-triton-riscv') assert.equal(manifest.dsh.bundle.patch, './cordis.patch.yml') assert.ok(manifest.files.includes('index.js')) + assert.ok(manifest.files.includes('lib/client.js')) assert.ok(manifest.files.includes('cordis.patch.yml')) assert.ok(manifest.files.includes('policy.md')) + assert.equal(manifest.exports['./client'], './lib/client.js') + assert.equal(manifest.dsh.client.platform, 'web') +}) + +test('builds a bounded local workbench launch', () => { + const launch = resolveWorkbenchLaunch({ + TRITON_RISCV_CHECKOUT: '/tmp/triton-riscv', + TRITON_RISCV_MCP_PYTHON: '/tmp/venv/bin/python', + TRITON_RISCV_WORKBENCH_PORT: '9000', + }) + + assert.deepEqual(launch, { + command: '/tmp/venv/bin/python', + args: ['-m', 'codex_agent.platform', '--host', '127.0.0.1', '--port', '9000'], + cwd: '/tmp/triton-riscv', + url: 'http://127.0.0.1:9000', + }) + assert.equal(resolveWorkbenchLaunch({ TRITON_RISCV_WORKBENCH_AUTOSTART: '0' }), null) + assert.throws(() => resolveWorkbenchLaunch({}), /TRITON_RISCV_CHECKOUT/) + assert.throws( + () => resolveWorkbenchLaunch({ TRITON_RISCV_CHECKOUT: '/tmp/repo', TRITON_RISCV_WORKBENCH_PORT: '70000' }), + /between 1 and 65535/, + ) }) test('mounts the domain policy and the official MCP client', async () => { @@ -60,7 +86,14 @@ test('registers lifecycle guidance through the system prompt service', async () }, } - apply(ctx) + const previousAutostart = process.env.TRITON_RISCV_WORKBENCH_AUTOSTART + process.env.TRITON_RISCV_WORKBENCH_AUTOSTART = '0' + try { + apply(ctx) + } finally { + if (previousAutostart === undefined) delete process.env.TRITON_RISCV_WORKBENCH_AUTOSTART + else process.env.TRITON_RISCV_WORKBENCH_AUTOSTART = previousAutostart + } assert.equal(name, 'triton-riscv-domain-policy') assert.deepEqual(inject, ['systemPrompt']) @@ -77,3 +110,44 @@ test('registers lifecycle guidance through the system prompt service', async () assert.match(section.text, /Never weaken or replace an acceptance test/) assert.match(section.text, /verified-passed/) }) + +test('publishes a root-slot Harness client plugin', async () => { + const source = await readFile(new URL('../lib/client.js', import.meta.url), 'utf8') + let definition + const sandbox = { + URL, + URLSearchParams, + window: { + __ModuleLoader__: { + load(value) { + definition = value + }, + }, + }, + } + vm.runInNewContext(source, sandbox) + + assert.equal(definition.id, 'dsh-triton-riscv') + const client = definition.factory(specifier => { + assert.equal(specifier, 'react') + return { createElement: () => null } + }) + let registration + const ctx = { + effect(setup) { + setup() + }, + slots: { + register(options, component) { + registration = { options, component } + return () => {} + }, + }, + } + client.apply(ctx) + + assert.deepEqual(Array.from(client.inject), ['slots']) + assert.equal(registration.options.name, 'root') + assert.equal(registration.options.priority, -100) + assert.equal(typeof registration.component, 'function') +})