mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-25 08:26:22 +02:00
Add OS-aware runtime context for cross-platform shell execution
Detect the runtime platform and default shell at startup, inject platform context into assistant instructions, and replace hardcoded /bin/sh with the detected shell in command executors (cli + electron). Made-with: Cursor
This commit is contained in:
parent
a3e74931bf
commit
b5424d92f9
6 changed files with 162 additions and 8 deletions
|
|
@ -1,5 +1,8 @@
|
|||
import { skillCatalog } from "./skills/index.js";
|
||||
import { WorkDir as BASE_DIR } from "../../config/config.js";
|
||||
import { getRuntimeContext, getRuntimeContextPrompt } from "./runtime-context.js";
|
||||
|
||||
const runtimeContextPrompt = getRuntimeContextPrompt(getRuntimeContext());
|
||||
|
||||
export const CopilotInstructions = `You are an intelligent workflow assistant helping users manage their workflows in ${BASE_DIR}. You can also help the user with general tasks.
|
||||
|
||||
|
|
@ -39,6 +42,8 @@ When a user asks for ANY task that might require external capabilities (web sear
|
|||
- Use relative paths (no \${BASE_DIR} prefixes) when running commands or referencing files.
|
||||
- Keep user data safe—double-check before editing or deleting important resources.
|
||||
|
||||
${runtimeContextPrompt}
|
||||
|
||||
## Workspace access & scope
|
||||
- You have full read/write access inside \`${BASE_DIR}\` (this resolves to the user's \`~/.rowboat\` directory). Create folders, files, and agents there using builtin tools or allowed shell commands—don't wait for the user to do it manually.
|
||||
- If a user mentions a different root (e.g., \`~/.rowboatx\` or another path), clarify whether they meant the Rowboat workspace and propose the equivalent path you can act on. Only refuse if they explicitly insist on an inaccessible location.
|
||||
|
|
|
|||
69
apps/cli/src/application/assistant/runtime-context.ts
Normal file
69
apps/cli/src/application/assistant/runtime-context.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
export type RuntimeShellDialect = 'windows-cmd' | 'posix-sh';
|
||||
export type RuntimeOsName = 'Windows' | 'macOS' | 'Linux' | 'Unknown';
|
||||
|
||||
export interface RuntimeContext {
|
||||
platform: NodeJS.Platform;
|
||||
osName: RuntimeOsName;
|
||||
shellDialect: RuntimeShellDialect;
|
||||
shellExecutable: string;
|
||||
}
|
||||
|
||||
export function getExecutionShell(platform: NodeJS.Platform = process.platform): string {
|
||||
return platform === 'win32' ? (process.env.ComSpec || 'cmd.exe') : '/bin/sh';
|
||||
}
|
||||
|
||||
export function getRuntimeContext(platform: NodeJS.Platform = process.platform): RuntimeContext {
|
||||
if (platform === 'win32') {
|
||||
return {
|
||||
platform,
|
||||
osName: 'Windows',
|
||||
shellDialect: 'windows-cmd',
|
||||
shellExecutable: getExecutionShell(platform),
|
||||
};
|
||||
}
|
||||
|
||||
if (platform === 'darwin') {
|
||||
return {
|
||||
platform,
|
||||
osName: 'macOS',
|
||||
shellDialect: 'posix-sh',
|
||||
shellExecutable: getExecutionShell(platform),
|
||||
};
|
||||
}
|
||||
|
||||
if (platform === 'linux') {
|
||||
return {
|
||||
platform,
|
||||
osName: 'Linux',
|
||||
shellDialect: 'posix-sh',
|
||||
shellExecutable: getExecutionShell(platform),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
platform,
|
||||
osName: 'Unknown',
|
||||
shellDialect: 'posix-sh',
|
||||
shellExecutable: getExecutionShell(platform),
|
||||
};
|
||||
}
|
||||
|
||||
export function getRuntimeContextPrompt(runtime: RuntimeContext): string {
|
||||
if (runtime.shellDialect === 'windows-cmd') {
|
||||
return `## Runtime Platform (CRITICAL)
|
||||
- Detected platform: **${runtime.platform}**
|
||||
- Detected OS: **${runtime.osName}**
|
||||
- Shell used by executeCommand: **${runtime.shellExecutable}** (Windows Command Prompt / cmd syntax)
|
||||
- Use Windows command syntax for executeCommand (for example: \`dir\`, \`type\`, \`copy\`, \`move\`, \`del\`, \`rmdir\`).
|
||||
- Use Windows-style absolute paths when outside workspace (for example: \`C:\\Users\\...\`).
|
||||
- Do not assume macOS/Linux command syntax when the runtime is Windows.`;
|
||||
}
|
||||
|
||||
return `## Runtime Platform (CRITICAL)
|
||||
- Detected platform: **${runtime.platform}**
|
||||
- Detected OS: **${runtime.osName}**
|
||||
- Shell used by executeCommand: **${runtime.shellExecutable}** (POSIX sh syntax)
|
||||
- Use POSIX command syntax for executeCommand (for example: \`ls\`, \`cat\`, \`cp\`, \`mv\`, \`rm\`).
|
||||
- Use POSIX paths when outside workspace (for example: \`~/Desktop\`, \`/Users/.../\` on macOS, \`/home/.../\` on Linux).
|
||||
- Do not assume Windows command syntax when the runtime is POSIX.`;
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
import { exec, execSync } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { getSecurityAllowList, SECURITY_CONFIG_PATH } from '../../config/security.js';
|
||||
import { getExecutionShell } from '../assistant/runtime-context.js';
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
const COMMAND_SPLIT_REGEX = /(?:\|\||&&|;|\||\n)/;
|
||||
const ENV_ASSIGNMENT_REGEX = /^[A-Za-z_][A-Za-z0-9_]*=.*/;
|
||||
const WRAPPER_COMMANDS = new Set(['sudo', 'env', 'time', 'command']);
|
||||
const EXECUTION_SHELL = getExecutionShell();
|
||||
|
||||
function sanitizeToken(token: string): string {
|
||||
return token.trim().replace(/^['"]+|['"]+$/g, '');
|
||||
|
|
@ -91,7 +93,7 @@ export async function executeCommand(
|
|||
cwd: options?.cwd,
|
||||
timeout: options?.timeout,
|
||||
maxBuffer: options?.maxBuffer || 1024 * 1024, // default 1MB
|
||||
shell: '/bin/sh', // use sh for cross-platform compatibility
|
||||
shell: EXECUTION_SHELL,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
@ -125,7 +127,7 @@ export function executeCommandSync(
|
|||
cwd: options?.cwd,
|
||||
timeout: options?.timeout,
|
||||
encoding: 'utf-8',
|
||||
shell: '/bin/sh',
|
||||
shell: EXECUTION_SHELL,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue