(input: string, sessionId: string, cwd: string, config?: any)
| 43 | } |
| 44 | |
| 45 | export async function handleSlashCommand(input: string, sessionId: string, cwd: string, config?: any): Promise<SlashResult> { |
| 46 | const trimmed = input.trim(); |
| 47 | if (!trimmed.startsWith('/')) return { handled: false }; |
| 48 | |
| 49 | const [cmd, ...args] = trimmed.slice(1).split(/\s+/); |
| 50 | const arg = args.join(' '); |
| 51 | |
| 52 | // A real slash command is `/name` where name is a plain identifier (letters, digits, |
| 53 | // -, _). Anything else that merely starts with '/' — an absolute file path like |
| 54 | // `/Users/me/pic.png ...`, a URL, etc. — is NOT a command. Let it through as a normal |
| 55 | // message so the agent (and tools like vision_analyze) can act on it, instead of |
| 56 | // erroring "Unknown command". Typos like `/helpp` still match the shape → handled below. |
| 57 | if (!/^[a-zA-Z][\w-]*$/.test(cmd ?? '')) { |
| 58 | return { handled: false }; |
| 59 | } |
| 60 | |
| 61 | switch (cmd) { |
| 62 | case 'btw': { |
| 63 | // When a task is running, `/btw` is intercepted in the UI and injected live. |
| 64 | // Reaching here means it was typed while IDLE — there's nothing to steer. |
| 65 | return { |
| 66 | handled: true, |
| 67 | message: '/btw steers a task that is already running. Nothing is running right now — just type your request normally, or run a task and use /btw <note> mid-flight to nudge it.', |
| 68 | }; |
| 69 | } |
| 70 | case 'flywheel': { |
| 71 | const { countTrajectories, getTrajectoryDatasetPath } = await import('../agent/trajectory.js'); |
| 72 | const enabled = (config as any)?.flywheel?.enabled === true; |
| 73 | const sandboxOn = (config as any)?.sandbox?.enabled === true; |
| 74 | const count = await countTrajectories(cwd); |
| 75 | const datasetPath = getTrajectoryDatasetPath(cwd); |
| 76 | const lines = [ |
| 77 | `Data flywheel: ${enabled ? 'ON' : 'OFF'}${enabled && !sandboxOn ? ' (but needs sandbox.enabled too!)' : ''}`, |
| 78 | `Recorded trajectories for this project: ${count}`, |
| 79 | `Dataset: ${datasetPath}`, |
| 80 | '', |
| 81 | enabled |
| 82 | ? 'Each successful sandboxed+merged task appends one training example (local only).' |
| 83 | : 'Enable with flywheel.enabled: true AND sandbox.enabled: true in ~/.qodex/config.yaml.', |
| 84 | ]; |
| 85 | return { handled: true, message: lines.join('\n') }; |
| 86 | } |
| 87 | |
| 88 | case 'trellis': { |
| 89 | const sub = arg.trim().toLowerCase(); |
| 90 | const { loadTrellisContext } = await import('../context/trellis.js'); |
| 91 | if (sub === 'init') { |
| 92 | // Scaffold an empty .trellis/ tree so the user (or the official Trellis |
| 93 | // CLI) can fill it. We only create dirs + a starter spec; we never |
| 94 | // overwrite existing files. |
| 95 | const fs = await import('fs'); |
| 96 | const path = await import('path'); |
| 97 | const base = path.join(cwd, '.trellis'); |
| 98 | const made: string[] = []; |
| 99 | for (const d of ['spec', 'tasks', 'workspace']) { |
| 100 | const dir = path.join(base, d); |
| 101 | if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); made.push(`.trellis/${d}/`); } |
| 102 | } |
no test coverage detected