diff --git a/src/servers/api/command-hq/directory.ts b/src/servers/api/command-hq/directory.ts index 94927408..4ee8ca6a 100644 --- a/src/servers/api/command-hq/directory.ts +++ b/src/servers/api/command-hq/directory.ts @@ -230,16 +230,9 @@ export function commanderClaudeMd(params: CommanderClaudeMdParams): string { 'those live here, in your own directory. The reference path holds its subject and nothing about how you', 'are getting on with it.', '', - 'Everything an action produces goes in THIS directory, by default and without discussion. Make a', - 'directory here for it — the layout below — and put the results there. Anywhere else is a dispatch', - 'decision, made later and only because the owner asked for it. Do not raise it on the first turn.', - '', - '**That holds even when the action itself writes somewhere else.** A platform task decides where it', - 'puts its own output, and a task built for a person clicking a file will often leave the result beside', - 'that file. That is right for the person and wrong for you: it puts your work in the middle of the', - 'thing you are watching, where the owner did not ask for it and where your next pass has to tell your', - 'own output apart from your subject. When an action leaves its result outside this directory, MOVE it', - 'here. Every time, without asking.', + 'Everything an action produces goes in THIS directory, under `classification/`. Never into the path you', + 'watch — that holds its subject, not your work. Getting the result there is your problem to solve with', + 'whatever ran the action; where it ends up is not negotiable.', '', '**A spawned agent follows a FILE, not a prompt.** Put the rules in `procedures/.md` here and', 'point the agent at it. When the rule changes you edit one document; a prompt baked into a loop is a', diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx index eca94744..24733931 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx @@ -279,6 +279,8 @@ export type TaskInputDef = { default?: string; options?: string[]; autofill?: string; + /** For a caller with no browser — never rendered, never asked about. See `useTasks.ts`. */ + hidden?: boolean; }; type TaskInputFormProps = { @@ -332,7 +334,7 @@ export const TaskInputForm = ({ hideTrackPickers, }: TaskInputFormProps) => { const { t } = useTranslation(); - const configurableInputs = Object.entries(inputDefs).filter(([key]) => !autoFilledKeys.has(key)); + const configurableInputs = Object.entries(inputDefs).filter(([key, def]) => !autoFilledKeys.has(key) && !def.hidden); if (configurableInputs.length === 0) return null; return ( diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/useTasks.test.ts b/src/workspaces/officerdev/src/apps/FileBrowser/useTasks.test.ts index 4c841e26..c8d96809 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/useTasks.test.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/useTasks.test.ts @@ -104,3 +104,14 @@ test('agentic and pipeline always take the modal', () => { expect(runsWithoutPrompting(scriptTask({ mode: 'agentic' }))).toBe(false); expect(runsWithoutPrompting(scriptTask({ mode: 'pipeline' }))).toBe(false); }); + +// An input that exists for a caller with no browser — a commander passing its own output directory. +// Declaring one must not turn a direct right-click run back into a modal with a field nobody clicking a +// file has an answer for. +test('a hidden input is not something to ask a person about', () => { + expect( + runsWithoutPrompting( + scriptTask({ inputs: { file_path: { required: true }, output_dir: { required: false, hidden: true } } }), + ), + ).toBe(true); +}); diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/useTasks.ts b/src/workspaces/officerdev/src/apps/FileBrowser/useTasks.ts index a66eb883..9526f1b1 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/useTasks.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/useTasks.ts @@ -11,6 +11,14 @@ export type TaskInputSpec = { autofill?: string; required?: boolean; options?: unknown; + /** + * Not for a person. An input that exists for callers with no browser — a commander passing its own + * output directory — and that the file browser must neither show nor count as something to ask about. + * + * Without this, declaring one turns a direct right-click run back into a modal with a field nobody + * clicking a file has an answer for. + */ + hidden?: boolean; }; export type TaskSummary = { @@ -55,7 +63,7 @@ export type TaskSummary = { const BROWSER_AUTOFILL = new Set(['entry_name', 'entry_path']); const isFilledByBrowser = (key: string, spec: TaskInputSpec): boolean => - key === 'file_path' || (!!spec.autofill && BROWSER_AUTOFILL.has(spec.autofill)); + spec.hidden === true || key === 'file_path' || (!!spec.autofill && BROWSER_AUTOFILL.has(spec.autofill)); export const runsWithoutPrompting = (task: TaskSummary): boolean => { if (task.mode !== 'script') return false;