Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/mcode-harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This companion package lets the agent host run MiniMax Code with OpenAgentCore's workspace. MiniMax Code keeps its own ACP Session, model loop and history. The package supplies a trusted MCP server (`bridge.mjs`), which the daemon registers as `oac_workspace` (its MCP server info names it `oac-workspace`). It exposes six native MiniMax Code tools rooted at the Session's workspace: `workspace_read`, `workspace_write`, `workspace_edit`, `workspace_bash`, `workspace_grep` and `workspace_glob`. The bridge and its tools run beside the CLI in the Session's agent-host view, where Bash, `rg` and `git` run in the sandbox; the package adds no inner sandbox. The [maintainer guide](../../docs/maintainers.md#runtime-images-and-helpers) owns the agent-host image build; [Harness qualification](../../contracts/agents-api/harness-onboarding.md#qualify-the-view) owns deployment evidence.

One patch script (`patch-native.mjs`) makes four edits to the pinned native CLI source. The SQLite task-admission transaction enforces the daemon's Subagent concurrency limit before child work starts; foreground, background, nested and idle-child append admissions share that transaction, and terminal native tasks release capacity. ACP initialization reports `oac/subagents` metadata: its version, the applied workspace tool policy and the admission limit. The native tool catalog applies the `protected-mcp-v1` tool gate described under [Subagents and cancellation](#subagents-and-cancellation). Under the same policy, the CLI ignores the workspace's project `.mcp.json`, so the Session's MCP comes only from the daemon. No second model or scheduling loop is introduced. Hosted public execution is not qualified by this package alone.
One patch script (`patch-native.mjs`) patches the pinned native CLI source. The SQLite task-admission transaction enforces the daemon's Subagent concurrency limit before child work starts; foreground, background, nested and idle-child append admissions share that transaction, and terminal native tasks release capacity. ACP initialization reports `oac/subagents` metadata: its version, the applied workspace tool policy and the admission limit. The native tool catalog applies the `protected-mcp-v1` tool gate described under [Subagents and cancellation](#subagents-and-cancellation). Under the same policy, the CLI ignores the workspace's project `.mcp.json`, so the Session's MCP comes only from the daemon. No second model or scheduling loop is introduced. Hosted public execution is not qualified by this package alone.

## Workspace tools

Expand All @@ -18,21 +18,21 @@ For each tool call, the bridge starts `launch.mjs` with the Session's private pr
MCODE_NATIVE_SOURCE=/absolute/upstream/checkout MCODE_CLI_DIR=/absolute/pinned/package bash scripts/build-mcode-harness.sh
```

This standalone companion uses its own npm lock and is excluded from the root pnpm workspace. The build archives the exact source revision, bundles its native tools and installs pinned MCP dependencies. The same source archive builds the CLI with its upstream build script and lockfile; the pinned package supplies only native runtime dependencies. `native-patch.json` in the artifact records the upstream revision and exact patch hashes, and `provenance.json` the hash of every artifact file. The artifact lands in `MCODE_HARNESS_BUILD_DIR`, or a new `${OAC_DEV_HOME:-$HOME/.oac}/build/mcode-harness-<timestamp>` directory. Runtime packaging uses this single CLI artifact and installs it immutably at `/opt/mcode-harness`.
This standalone companion uses its own npm lock and is excluded from the root pnpm workspace. The build archives the exact source revision, bundles its native tools and installs pinned MCP dependencies. The build verifies tool guidance with the actual patched native prompt renderer and templates before compiling the CLI with its upstream build script and lockfile; the pinned package supplies only native runtime dependencies. `native-patch.json` in the artifact records the upstream revision and exact patch hashes, and `provenance.json` the hash of every artifact file. The artifact lands in `MCODE_HARNESS_BUILD_DIR`, or a new `${OAC_DEV_HOME:-$HOME/.oac}/build/mcode-harness-<timestamp>` directory. Runtime packaging uses this single CLI artifact and installs it immutably at `/opt/mcode-harness`.

## Subagents and cancellation

`subagent-snapshot.mjs` is a daemon-only reader of the private native SQLite history. It opens a read-only transaction, scopes recursive descendants to the bound root, and fails explicitly if a complete snapshot exceeds its bounds. It never executes a model or exposes native history to workspace tools. The adapter freezes root output and keeps the same ACP owner for bounded child settlement. A completed or idle task remains an active public Subagent; native abort is a Turn cancellation and never implies a closed Subagent.

The bridge owns each launcher until exit. MCP cancellation and transport shutdown stop all owned workers before releasing the bridge. The outer Runtime owns the native process group. Both boundaries require real Docker cancellation tests.

The daemon sets the protected `protected-mcp-v1` tool policy independently of the concurrency limit. The native catalog applies it to root and child profiles, withholding direct native filesystem and process tools. The Session-private `oac_workspace` MCP server supplies the authorized workspace tools to workers. Other MCP servers keep their native selection rules. Native Explore and Verifier profiles keep their stricter native capability ceiling. ACP initialization reports the applied policy and admission limit; enabled Subagents reject an unpatched CLI before accepting model input.
The daemon sets the protected `protected-mcp-v1` tool policy independently of the concurrency limit. The native catalog applies it to root and child profiles, withholding direct native filesystem and process tools. The Session-private `oac_workspace` MCP server supplies the authorized workspace tools to workers. Other MCP servers keep their native selection rules. The same authored policy filters native prompt capabilities, so the ACP root and child templates name builtin workspace tools only when they are available; MCP tool names and schemas come from their declarations. Native Explore and Verifier profiles keep their stricter native capability ceiling. ACP initialization reports the applied policy and admission limit; enabled Subagents reject an unpatched CLI before accepting model input.

Native tool schemas are retained. Text and image results use standard MCP content; video results are rejected. The pinned native CLI may add task and Skill utility tools, so qualification must inspect the actual inventory rather than assume exactly six.

## Tests

`make check-mcode-harness` runs the package's Node tests and syntax checks. Qualify changes with the [Harness acceptance checklist](../../contracts/agents-api/harness-onboarding.md#qualify-the-adapter); synthetic probes and native model runs do not complete public Files/Artifacts or independent Core acceptance.
`make check-mcode-harness` runs the package's Node tests and syntax checks. The native prompt regression requires `MCODE_SOURCE` pointing to the pinned, patched native source with upstream dependencies installed; the companion build always runs it. It checks ordinary native guidance, disabled local tools and protected root and child capabilities without a model. Qualify changes with the [Harness acceptance checklist](../../contracts/agents-api/harness-onboarding.md#qualify-the-adapter); synthetic probes and native model runs do not complete public Files/Artifacts or independent Core acceptance.

For the packaged Linux regression, provide an operator-owned private profile and artifact directory, then run `native.test.mjs` inside the qualified Docker Runtime:

Expand Down
68 changes: 68 additions & 0 deletions packages/mcode-harness/native-prompt.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { join } from 'node:path';

// The native build supplies its pinned source and dependencies. No model is used.
const root = process.env.MCODE_SOURCE;
test('native prompt guidance follows the protected tool policy', { skip: !root }, async () => {
const require = createRequire(join(root, 'package.json'));
const { build } = require('esbuild');
const capabilities = join(root, 'packages/config/src/agent-capabilities.ts');
const renderer = join(root, 'packages/local-runtime-v2/src/service/agent/builtin/prompt-renderer.ts');
const paths = JSON.parse(readFileSync(join(root, 'tsconfig.standalone.json'))).compilerOptions.paths;
const result = await build({
stdin: { contents: `export * from ${JSON.stringify(capabilities)}; export * from ${JSON.stringify(renderer)};`, resolveDir: root },
bundle: true, write: false, platform: 'node', format: 'esm', nodePaths: [join(root, 'node_modules')],
banner: { js: `import { createRequire } from 'node:module'; const require = createRequire(${JSON.stringify(join(root, 'package.json'))});` },
plugins: [{ name: 'native-prompt-imports', setup(builder) {
builder.onResolve({ filter: /^@mavis\// }, ({ path }) => {
// Import the same config implementation without unrelated barrel exports.
if (path === '@mavis/config') return { path: capabilities };
if (paths[path]) return { path: join(root, paths[path][0]) };
});
} }],
});
const native = await import('data:text/javascript;base64,' + Buffer.from(result.outputFiles[0].text).toString('base64'));
const templates = new Map(['AGENT_CONTEXT.md.hbs', 'tui/SYSTEM.md.hbs', 'explore.md.hbs'].map(file =>
[file, readFileSync(join(root, 'packages/local-runtime-v2/assets/agents/_v2', file), 'utf8')]));
const context = (tools, child = false) => ({
...native.createBuiltinPromptContext({ capabilities: native.resolveAgentCapabilities({ tools }),
promptProfile: 'tui', surface: child ? 'task-child' : 'cli' }, {}),
layer: { base: true, worker: false, root: false, branch: false },
});
const render = (file, value) => native.renderBuiltinTemplate(templates.get(file), value, file);
const previous = process.env.OAC_RUNTIME_MCODE_TOOL_POLICY;
try {
delete process.env.OAC_RUNTIME_MCODE_TOOL_POLICY;
const normal = context(undefined);
assert.equal(normal.tools.bash, true);
for (const file of ['AGENT_CONTEXT.md.hbs', 'tui/SYSTEM.md.hbs']) {
assert.match(render(file, normal), /Prefer dedicated tools over `bash` whenever one fits\. Use `grep`/);
assert.match(render(file, normal), /Reserve `bash` for shell-only operations/);
}
const withoutBash = context(['read', 'grep', 'glob']);
for (const file of templates.keys()) assert.doesNotMatch(render(file, withoutBash), /`bash`/);

process.env.OAC_RUNTIME_MCODE_TOOL_POLICY = 'protected-mcp-v1';
for (const value of [context([]), context(undefined), context(['bash', 'read', 'grep', 'glob'], true)]) {
for (const name of ['bash', 'read', 'write', 'edit', 'grep', 'glob']) {
assert.equal(value.tools[name], false, name);
assert.equal(native.isProtectedWorkspaceToolAllowed(name, 'builtin'), false, name);
}
for (const file of templates.keys()) assert.doesNotMatch(render(file, value), /`(?:bash|read|write|edit|grep|glob)`/, file);
}
assert.equal(native.isProtectedWorkspaceToolAllowed('web_search', 'builtin-matrix'), false);
for (const name of ['skill', 'task', 'task_append', 'task_query', 'task_output', 'task_stop']) {
assert.equal(native.isProtectedWorkspaceToolAllowed(name, 'builtin'), true, name);
}
// The policy does not rename, alias or restrict configured MCP definitions.
for (const name of ['mcp__oac_workspace__workspace_bash', 'mcp__other__lookup']) {
assert.equal(native.isProtectedWorkspaceToolAllowed(name, 'configured'), true, name);
}
} finally {
if (previous === undefined) delete process.env.OAC_RUNTIME_MCODE_TOOL_POLICY;
else process.env.OAC_RUNTIME_MCODE_TOOL_POLICY = previous;
}
});
43 changes: 39 additions & 4 deletions packages/mcode-harness/patch-native.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ if (acp.split(marker).length !== 2) throw new Error('Pinned native ACP initializ
writeFileSync(acpPath, acp.replace(marker, " _meta: {\n 'oac/subagents': { version: 1, workspaceTools: process.env.OAC_RUNTIME_MCODE_TOOL_POLICY ?? null, maxConcurrent: Number(process.env.OAC_RUNTIME_MCODE_MAX_SUBAGENTS ?? 0) },\n 'minimax-code/extensions': {"));
const catalogPath = join(root, 'packages/local-runtime-v2/src/service/turn-system/agent-host/assembly/local-turn-tool-catalog.ts');
const catalog = readFileSync(catalogPath, 'utf8');
const capabilityPath = join(root, 'packages/config/src/agent-capabilities.ts');
const capabilities = readFileSync(capabilityPath, 'utf8');
const enabled = ' return capabilities.tools === undefined || capabilities.tools.includes(toolName);';
if (capabilities.split(enabled).length !== 2) throw new Error('Pinned native tool capability predicate changed');
writeFileSync(capabilityPath, capabilities.replace(enabled,
" return isProtectedWorkspaceToolAllowed(toolName, 'builtin') && (capabilities.tools === undefined || capabilities.tools.includes(toolName));") + `
// The protected workspace policy applies to both tool admission and prompt capabilities.
export function isProtectedWorkspaceToolAllowed(toolName: string, source: string): boolean {
if (process.env.OAC_RUNTIME_MCODE_TOOL_POLICY !== 'protected-mcp-v1') return true;
if (source === 'builtin-matrix') return false;
return source !== 'builtin' || ['skill', 'task', 'task_append', 'task_query', 'task_output', 'task_stop'].includes(toolName);
}
`);
const configIndexPath = join(root, 'packages/config/src/index.ts');
const configIndex = readFileSync(configIndexPath, 'utf8');
if (configIndex.split(' isAgentBuiltinToolEnabled,').length !== 2) throw new Error('Pinned native config exports changed');
writeFileSync(configIndexPath, configIndex.replace(' isAgentBuiltinToolEnabled,', ' isAgentBuiltinToolEnabled,\n isProtectedWorkspaceToolAllowed,'));
const gate = " if (!isFeatureEnabled(ceiling, toolName)) return false;";
if (catalog.split(gate).length !== 2) throw new Error('Pinned native tool capability gate changed');
const selectorGate = " if (!input.selector.allowsTool(input.tool.def.name, input.options.selectorAlias)) return false;\n if (!isMcpServerAllowed(input.selector, input.options)) return false;";
Expand All @@ -33,11 +50,29 @@ const withWorkspace = catalog.replace(selectorGate, ` const protectedWorkspace
input.source === 'configured' && input.options.mcp === true && input.options.serverName === 'oac_workspace';
if (!protectedWorkspace && !input.selector.allowsTool(input.tool.def.name, input.options.selectorAlias)) return false;
if (!protectedWorkspace && !isMcpServerAllowed(input.selector, input.options)) return false;`);
writeFileSync(catalogPath, withWorkspace.replace(gate, ` if (process.env.OAC_RUNTIME_MCODE_TOOL_POLICY === 'protected-mcp-v1') {
if (source === 'builtin' && toolName !== 'skill' && !DELEGATION_TOOL_NAMES.has(toolName) && !TASK_CONTROL_TOOL_NAMES.has(toolName)) return false;
if (source === 'builtin-matrix') return false;
}
writeFileSync(catalogPath, "import { isProtectedWorkspaceToolAllowed } from '@mavis/config';\n" + withWorkspace.replace(gate, ` if (!isProtectedWorkspaceToolAllowed(toolName, source)) return false;
${gate}`));
const rendererPath = join(root, 'packages/local-runtime-v2/src/service/agent/builtin/prompt-renderer.ts');
const renderer = readFileSync(rendererPath, 'utf8');
if (renderer.split(' tools,').length !== 2) throw new Error('Pinned native prompt context changed');
writeFileSync(rendererPath, renderer.replace(' tools,', ` tools,
nativeWorkspaceTools: tools.bash && tools.grep && tools.glob && tools.read && tools.edit && tools.write,`));
for (const file of ['AGENT_CONTEXT.md.hbs', 'tui/SYSTEM.md.hbs']) {
const path = join(root, 'packages/local-runtime-v2/assets/agents/_v2', file);
const template = readFileSync(path, 'utf8');
const guidance = /- Prefer dedicated tools over `bash` whenever one fits\.[\s\S]*?no available dedicated tool can complete the task\./g;
const matches = [...template.matchAll(guidance)];
if (matches.length !== (file === 'AGENT_CONTEXT.md.hbs' ? 2 : 1)) throw new Error('Pinned native workspace guidance changed');
writeFileSync(path, template.replace(guidance, paragraph => '{{#if nativeWorkspaceTools}}' + paragraph + '{{else}}- Prefer available dedicated tools for file operations and search. Use the tools declared for this turn.{{/if}}')
.replace('- For unfamiliar project-specific concepts, search the workspace with `grep` or `glob` first.', '{{#if tools.grep}}{{#if tools.glob}}- For unfamiliar project-specific concepts, search the workspace with `grep` or `glob` first.{{/if}}{{/if}}'));
}
const explorePath = join(root, 'packages/local-runtime-v2/assets/agents/_v2/explore.md.hbs');
const explore = readFileSync(explorePath, 'utf8');
const bashAdvice = 'Use `bash` only to inspect existing code or Git state. ';
const backgroundAdvice = 'Explore does not have `task_output`, so do not use `run_in_background` with\n`bash`. Run each Bash command in the foreground and return its result.';
if (!explore.includes('# Read-only Bash') || !explore.includes(bashAdvice) || !explore.includes(backgroundAdvice)) throw new Error('Pinned native Explore guidance changed');
writeFileSync(explorePath, explore.replace('# Read-only Bash', '# Read-only {{#if tools.bash}}Bash{{else}}execution{{/if}}').replace(bashAdvice, '{{#if tools.bash}}' + bashAdvice + '{{/if}}')
.replace(backgroundAdvice, '{{#if tools.bash}}' + backgroundAdvice + '{{/if}}'));
const projectPath = join(root, 'packages/local-runtime-v2/src/service/mcp/project-mcp.service.ts');
const project = readFileSync(projectPath, 'utf8');
const projectGate = ' if (!context?.workspaceRoot || !context.sessionId) return {};';
Expand Down
1 change: 1 addition & 0 deletions scripts/build-mcode-harness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ test "$(node "$native/cli.js" --version)" = "$version"
MCODE_SOURCE="$context/upstream" node patch-native.mjs
cd upstream
corepack pnpm install --frozen-lockfile
MCODE_SOURCE="$context/upstream" node --test "$context/native-prompt.test.mjs"
node scripts/build.mjs
)
(
Expand Down
Loading