From af41144689cf01d9a75a792ce696d24f93807179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 25 Aug 2026 04:59:06 +0000 Subject: [PATCH] the commander is told the rule, not the API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit three sentences where there were fourteen. everything an action produces goes in this directory under classification/, never into the path being watched, and getting it there is the commander's problem to solve with whatever ran the action. what came out was me teaching it the transcription API — read the task's inputs, here is the endpoint that lists them, pass a destination. that is one task's detail in a file every commander reads, and the commander can discover it. the rule is the part only this file can state. the task half stays and is real: transcribe-audio-file takes an optional output_dir now, marked `hidden` so the file browser neither renders it nor counts it as something to ask a person about. without that, declaring it would have turned the direct right-click run back into a modal with a field nobody clicking a file has an answer for. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- src/servers/api/command-hq/directory.ts | 13 +++---------- .../FileBrowserApp/components/TaskRunnerModal.tsx | 4 +++- .../src/apps/FileBrowser/useTasks.test.ts | 11 +++++++++++ .../officerdev/src/apps/FileBrowser/useTasks.ts | 10 +++++++++- 4 files changed, 26 insertions(+), 12 deletions(-) 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;