an explicit per-line exemption, and command hq keyed (383 -> 344)

Adds `// i18n-exempt: <reason>`, which exempts the next line. The reason is mandatory
-- the regex will not match a bare marker -- so somebody has to write down why, and
`grep -rn i18n-exempt` lists every one for review.

It exists because CommandHQ's startBrief is a prompt written FOR THE MODEL: the person
sees a short displayText instead, and translating it would change how the agent
behaves. No syntactic rule can tell that from prose, and the alternative was a seventh
scanner heuristic. <Untranslated> covers JSX, this covers everything else, and together
they should mean the scanner needs no further special cases.

The new-commander guide was module-level data whose `body` paragraphs the scanner never
saw -- eight paragraphs of real user-facing prose. It carries titleKey and bodyKeys now
and the renderer resolves them.

Four implicit-return arrows needed bodies to hold the hook.
This commit is contained in:
2026-08-23 15:03:25 +00:00
parent d0904b2c84
commit 1e268d553d
7 changed files with 243 additions and 121 deletions
@@ -6,6 +6,7 @@ import { errorText } from 'helpers/error-text';
import { categoryFromParam, commandHqListPath } from './command-hq-routes';
import { useCommandHqCategories } from './useCommandHq';
import type { CommandHqCategoryFilter } from './types';
import { useTranslation } from '../../locales';
// Left panel of the /command-hq workspace — a flat category list, click to filter the grid. Deliberately
// simple for v1: no nesting, no search, no drag-to-reorder — see `docs/command-hq.md` → "Layout, v1".
@@ -32,6 +33,7 @@ const Row = ({ label, active, to, count }: RowProps) => (
);
export const CommandHqCategories = () => {
const { t } = useTranslation();
const [searchParams] = useSearchParams();
const active: CommandHqCategoryFilter = categoryFromParam(searchParams.get('category'));
const { categories, createCategory } = useCommandHqCategories();
@@ -70,20 +72,22 @@ export const CommandHqCategories = () => {
*/
<div className="flex h-full min-h-0 flex-col bg-muted/30">
<div className="flex shrink-0 items-center justify-between border-b border-border px-3 py-2">
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Categories</span>
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t('commandHq.categories')}
</span>
<button
type="button"
onClick={() => setAdding(true)}
className="rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
title="New category"
title={t('commandHq.newCategory')}
>
<Plus className="h-3.5 w-3.5" />
</button>
</div>
<nav className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto p-2">
<Row label="All commanders" active={active === undefined} to={commandHqListPath(undefined)} />
<Row label="Uncategorised" active={active === 'none'} to={commandHqListPath('none')} />
<Row label={t('commandHq.allCommanders')} active={active === undefined} to={commandHqListPath(undefined)} />
<Row label={t('commandHq.uncategorised')} active={active === 'none'} to={commandHqListPath('none')} />
{categories.map((category) => (
<Row
key={category.id}
@@ -106,13 +110,13 @@ export const CommandHqCategories = () => {
setAdding(false);
}
}}
placeholder="Category name"
placeholder={t('commandHq.categoryName')}
className="mx-1 rounded-lg border border-border bg-background px-2 py-1.5 text-sm outline-none focus:border-primary"
/>
</form>
)}
{!adding && categories.length === 0 && (
<div className="px-3 py-1 text-xs text-muted-foreground">No categories yet</div>
<div className="px-3 py-1 text-xs text-muted-foreground">{t('commandHq.noCategories')}</div>
)}
</nav>
</div>
@@ -20,6 +20,7 @@ import {
} from './useCommandHq';
import { formatCommanderCost, formatTokens } from './format';
import { NEW_COMMANDER_GUIDE } from './newCommanderGuide';
import { useTranslation } from '../../locales';
// Right panel of the /command-hq workspace. Three states, chosen by the URL alone (no channel — panels
// read the route directly, per `docs/navigation-audit.md`):
@@ -33,10 +34,11 @@ import { NEW_COMMANDER_GUIDE } from './newCommanderGuide';
const CHAT_TAIL = 20;
function EmptyState() {
const { t, tUi } = useTranslation();
return (
<div className="flex h-full flex-col items-center justify-center gap-2 text-center text-sm text-muted-foreground">
<Radar className="h-8 w-8 opacity-40" />
<p>Select a commander, or start a new one.</p>
<p>{t('commandHq.selectOne')}</p>
</div>
);
}
@@ -48,29 +50,32 @@ function EmptyState() {
* carries a visible marker so a guide that is still being written cannot be mistaken for one that is done,
* which matters because this ships on every deploy whether the copy is ready or not.
*/
const NewCommanderGuide = () => (
<div className="shrink-0 border-b border-border px-5 py-4">
<div className="mx-auto flex max-w-2xl flex-col gap-4">
{NEW_COMMANDER_GUIDE.map((section) => (
<section key={section.title}>
<h3 className="flex items-center gap-2 text-sm font-semibold text-foreground">
{section.title}
{section.provisional && (
<span className="rounded bg-warning/15 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-warning">
draft
</span>
)}
</h3>
{section.body.map((paragraph) => (
<p key={paragraph} className="mt-1 text-sm leading-relaxed text-muted-foreground">
{paragraph}
</p>
))}
</section>
))}
const NewCommanderGuide = () => {
const { t, tUi } = useTranslation();
return (
<div className="shrink-0 border-b border-border px-5 py-4">
<div className="mx-auto flex max-w-2xl flex-col gap-4">
{NEW_COMMANDER_GUIDE.map((section) => (
<section key={section.titleKey}>
<h3 className="flex items-center gap-2 text-sm font-semibold text-foreground">
{t(section.titleKey)}
{section.provisional && (
<span className="rounded bg-warning/15 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-warning">
{t('commandHq.guide.draft')}
</span>
)}
</h3>
{section.bodyKeys.map((bodyKey) => (
<p key={bodyKey} className="mt-1 text-sm leading-relaxed text-muted-foreground">
{t(bodyKey)}
</p>
))}
</section>
))}
</div>
</div>
</div>
);
);
};
/**
* The whole opening move, as one button.
@@ -86,6 +91,7 @@ const NewCommanderGuide = () => (
*/
const startBrief = (name: string): string =>
[
// i18n-exempt: agent brief — the MODEL reads this; the transcript shows displayText to the person
`You have been named "${name}", and that is the only thing I have told you about what I want.`,
'It is not decoration — I chose it before anything else, so treat it as the subject and read this',
'directory looking for what it refers to.',
@@ -112,20 +118,21 @@ const startBrief = (name: string): string =>
'Take as long as you like reading first. The constraint is on what you say, not on what you do.',
].join('\n');
const StartCommanderButton = ({ ready, blockedBy, onStart }: StartCommanderButtonProps) => (
<div className="shrink-0 border-b border-border px-5 py-4">
<div className="mx-auto flex max-w-2xl flex-col items-start gap-2">
<Button onClick={onStart} disabled={!ready} size="lg">
Start the conversation
</Button>
<p className="text-xs text-muted-foreground">
{ready
? 'It will look around this directory, then ask you what to watch for.'
: `${blockedBy} — then this starts it.`}
</p>
const StartCommanderButton = ({ ready, blockedBy, onStart }: StartCommanderButtonProps) => {
const { t, tUi } = useTranslation();
return (
<div className="shrink-0 border-b border-border px-5 py-4">
<div className="mx-auto flex max-w-2xl flex-col items-start gap-2">
<Button onClick={onStart} disabled={!ready} size="lg">
{t('commandHq.startConversation')}
</Button>
<p className="text-xs text-muted-foreground">
{ready ? t('commandHq.startHint') : t('commandHq.startBlocked', { missing: blockedBy })}
</p>
</div>
</div>
</div>
);
);
};
type StartCommanderButtonProps = {
ready: boolean;
@@ -155,47 +162,50 @@ const CommanderSetup = ({
onName: (next: string) => void;
cwd: string | null;
onPickDirectory: () => void;
}) => (
<div className="shrink-0 border-b border-border bg-muted/20 px-5 py-4">
<div className="mx-auto flex max-w-2xl flex-col gap-3">
<div className="flex flex-col gap-1">
<label htmlFor="commander-name" className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
Name
</label>
<input
id="commander-name"
autoFocus
value={name}
onChange={(ev) => onName(ev.target.value)}
placeholder="Platform deploys"
className="rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none focus:border-primary"
/>
{/* Said here because it is not obvious and it is not reversible cheaply: the harness would otherwise
}) => {
const { t, tUi } = useTranslation();
return (
<div className="shrink-0 border-b border-border bg-muted/20 px-5 py-4">
<div className="mx-auto flex max-w-2xl flex-col gap-3">
<div className="flex flex-col gap-1">
<label htmlFor="commander-name" className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t('commandHq.name')}
</label>
<input
id="commander-name"
autoFocus
value={name}
onChange={(ev) => onName(ev.target.value)}
placeholder={t('commandHq.platformDeploys')}
className="rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none focus:border-primary"
/>
{/* Said here because it is not obvious and it is not reversible cheaply: the harness would otherwise
name this after whatever the first message happens to say. */}
<span className="text-[11px] text-muted-foreground">
Applied to the conversation as it starts, instead of the harness naming it after your first message.
</span>
</div>
<span className="text-[11px] text-muted-foreground">{t('commandHq.nameHint')}</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Working directory</span>
<button
type="button"
onClick={onPickDirectory}
className={`flex items-center gap-2 rounded-lg border px-3 py-2 text-left text-sm transition-colors ${
cwd ? 'border-border bg-background' : 'border-dashed border-border text-muted-foreground hover:bg-muted/40'
}`}
>
<FolderOpen className="h-4 w-4 shrink-0" />
<span className="min-w-0 flex-1 truncate font-mono text-xs">{cwd ?? 'Choose a directory…'}</span>
</button>
<span className="text-[11px] text-muted-foreground">
Where every command it runs resolves from, and where its history is kept. It cannot be moved later.
</span>
<div className="flex flex-col gap-1">
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t('commandHq.workingDirectory')}
</span>
<button
type="button"
onClick={onPickDirectory}
className={`flex items-center gap-2 rounded-lg border px-3 py-2 text-left text-sm transition-colors ${
cwd
? 'border-border bg-background'
: 'border-dashed border-border text-muted-foreground hover:bg-muted/40'
}`}
>
<FolderOpen className="h-4 w-4 shrink-0" />
<span className="min-w-0 flex-1 truncate font-mono text-xs">{cwd ?? 'Choose a directory…'}</span>
</button>
<span className="text-[11px] text-muted-foreground">{t('commandHq.cwdHint')}</span>
</div>
</div>
</div>
</div>
);
);
};
/**
* A commander started in THIS pane, and it keeps the same live chat for as long as the pane is open.
@@ -223,6 +233,7 @@ function NewCommander({
category: number | 'none' | undefined;
onStarted: (id: string) => void;
}) {
const { t, tUi } = useTranslation();
const navigate = useNavigate();
/*
A name and a working directory, both required BEFORE the first message.
@@ -333,7 +344,7 @@ function NewCommander({
/>
) : (
<div className="flex shrink-0 items-center gap-2 border-b border-border bg-background/60 px-4 py-2 text-sm font-medium text-foreground/80">
New commander
{t('commandHq.new')}
</div>
)}
{/* `context` is a prop of EmbeddableChat, and it is NOT the `context` handed to `useChat` above.
@@ -407,13 +418,14 @@ function NewCommander({
* the normal case and does not need a box explaining that.
*/
const SpawnedChildren = ({ sessionId }: { sessionId: string }) => {
const { t, tUi } = useTranslation();
const { children, totalUsd, unknownCost } = useCommandHqChildren(sessionId);
if (!children.length) return null;
return (
<div className="shrink-0 border-b border-border px-4 py-2">
<div className="flex items-baseline gap-2 text-xs text-muted-foreground">
<span className="font-medium uppercase tracking-wide">Spawned</span>
<span className="font-medium uppercase tracking-wide">{t('commandHq.spawned')}</span>
<span>{children.length}</span>
{/*
Cost is stated as a floor when any child's is unknown, never as a confident total. A commander's
@@ -460,6 +472,7 @@ type MetaBarProps = {
};
function MetaBar({ sessionId, title, isConnected, isGenerating, onDisconnect }: MetaBarProps) {
const { t, tUi } = useTranslation();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const category = categoryFromParam(searchParams.get('category'));
@@ -511,7 +524,7 @@ function MetaBar({ sessionId, title, isConnected, isGenerating, onDisconnect }:
<button
type="button"
onClick={onDisconnect}
title="End session — releases it so it can be resumed elsewhere"
title={t('commandHq.endSession')}
className="ml-1 rounded p-1 hover:bg-muted hover:text-destructive"
>
<Unplug className="h-3.5 w-3.5" />
@@ -519,12 +532,12 @@ function MetaBar({ sessionId, title, isConnected, isGenerating, onDisconnect }:
)}
{confirmingDelete ? (
<span className="ml-1 flex items-center gap-1">
<span className="text-destructive">Delete?</span>
<span className="text-destructive">{t('commandHq.deleteQ')}</span>
<button
type="button"
onClick={() => void confirmDelete()}
title="Confirm delete — removes the conversation too, not just the Command HQ entry"
aria-label="Confirm delete"
title={t('commandHq.confirmDeleteHint')}
aria-label={t('commandHq.confirmDelete')}
className="rounded p-1 text-destructive hover:bg-destructive/10"
>
<Check className="h-3.5 w-3.5" />
@@ -532,8 +545,8 @@ function MetaBar({ sessionId, title, isConnected, isGenerating, onDisconnect }:
<button
type="button"
onClick={() => setConfirmingDelete(false)}
title="Cancel"
aria-label="Cancel delete"
title={tUi('button.cancel')}
aria-label={t('commandHq.cancelDelete')}
className="rounded p-1 opacity-50 hover:opacity-100"
>
<X className="h-3.5 w-3.5" />
@@ -543,8 +556,8 @@ function MetaBar({ sessionId, title, isConnected, isGenerating, onDisconnect }:
<button
type="button"
onClick={() => setConfirmingDelete(true)}
title="Delete commander"
aria-label="Delete commander"
title={t('commandHq.deleteCommander')}
aria-label={t('commandHq.deleteCommander')}
className="ml-1 rounded p-1 hover:bg-muted hover:text-destructive"
>
<Trash2 className="h-3.5 w-3.5" />
@@ -559,7 +572,7 @@ function MetaBar({ sessionId, title, isConnected, isGenerating, onDisconnect }:
onChange={(ev) => setCategory.mutate(ev.target.value === '' ? null : Number(ev.target.value))}
className="rounded border border-border bg-background px-1.5 py-0.5 text-xs"
>
<option value="">Uncategorised</option>
<option value="">{t('commandHq.uncategorised')}</option>
{categories.map((c) => (
<option key={c.id} value={c.id}>
{c.name}
@@ -620,6 +633,7 @@ type ResumeState = {
* Same shape here.
*/
function ResumedCommander({ sessionId, resume }: { sessionId: string; resume: ResumeState }) {
const { t, tUi } = useTranslation();
const chat = useChat(undefined, resume.model ?? undefined, {
replaceUrl: false,
context: 'command-hq',
@@ -651,6 +665,7 @@ function ResumedCommander({ sessionId, resume }: { sessionId: string; resume: Re
/** Fetches the transcript and nothing else. Mounts the chat only when it has one. */
function ExistingCommander({ sessionId }: { sessionId: string }) {
const { t, tUi } = useTranslation();
const client = useClient();
const [resume, setResume] = useState<ResumeState | undefined>(undefined);
const [resuming, setResuming] = useState(true);
@@ -683,7 +698,11 @@ function ExistingCommander({ sessionId }: { sessionId: string }) {
}, [sessionId]);
if (resuming || !resume) {
return <div className="flex h-full items-center justify-center text-sm text-muted-foreground">Loading…</div>;
return (
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
{t('commandHq.loading')}
</div>
);
}
return <ResumedCommander key={sessionId} sessionId={sessionId} resume={resume} />;
@@ -8,6 +8,7 @@ import { categoryFromParam, commandHqListPath, commandHqNewPath, commandHqSessio
import { useCommandHqCommanderActions, useCommandHqCommanders } from './useCommandHq';
import { formatCommanderCost, formatTokens } from './format';
import type { CommandHqCategoryFilter, CommandHqCommander } from './types';
import { useTranslation } from '../../locales';
// Right-middle panel of the /command-hq workspace — one card per commander in the selected category.
// Clicking a card opens the detail panel via the URL (`/command-hq/:sessionId`), never a channel — see
@@ -39,6 +40,7 @@ type CardProps = {
* control sits in its own stacking context (`z-10`) to intercept its own clicks first.
*/
const Card = ({ commander, to, selected, onDeleted }: CardProps) => {
const { t, tUi } = useTranslation();
const [confirming, setConfirming] = useState(false);
const { deleteCommander } = useCommandHqCommanderActions(commander.id);
@@ -84,16 +86,16 @@ const Card = ({ commander, to, selected, onDeleted }: CardProps) => {
<button
onClick={() => void confirmDelete()}
className="cursor-pointer rounded p-1 text-destructive hover:bg-destructive/10"
title="Confirm delete — removes the conversation too, not just the Command HQ entry"
aria-label={`Confirm deleting ${commander.title}`}
title={t('commandHq.confirmDeleteHint')}
aria-label={t('commandHq.confirmDeletingNamed', { name: commander.title })}
>
<Check className="h-3.5 w-3.5" />
</button>
<button
onClick={() => setConfirming(false)}
className="cursor-pointer rounded p-1 opacity-50 hover:opacity-100"
title="Cancel"
aria-label="Cancel delete"
title={tUi('button.cancel')}
aria-label={t('commandHq.cancelDelete')}
>
<X className="h-3.5 w-3.5" />
</button>
@@ -102,8 +104,8 @@ const Card = ({ commander, to, selected, onDeleted }: CardProps) => {
<button
onClick={() => setConfirming(true)}
className="absolute right-2 top-2 z-10 cursor-pointer rounded p-1 text-muted-foreground opacity-0 transition-opacity hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100"
title="Delete commander"
aria-label={`Delete ${commander.title}`}
title={t('commandHq.deleteCommander')}
aria-label={t('commandHq.deleteNamed', { name: commander.title })}
>
<Trash2 className="h-3.5 w-3.5" />
</button>
@@ -113,6 +115,7 @@ const Card = ({ commander, to, selected, onDeleted }: CardProps) => {
};
export const CommandHqGrid = () => {
const { t, tUi } = useTranslation();
const [searchParams] = useSearchParams();
const category: CommandHqCategoryFilter = categoryFromParam(searchParams.get('category'));
const { sessionId } = useParams<{ sessionId: string }>();
@@ -136,7 +139,7 @@ export const CommandHqGrid = () => {
className="flex items-center gap-1.5 rounded-lg bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:opacity-90"
>
<Plus className="h-3.5 w-3.5" />
New commander
{t('commandHq.new')}
</Link>
</div>
@@ -163,12 +166,12 @@ export const CommandHqGrid = () => {
{!isLoading && commanders.length === 0 && (
<div className="flex flex-1 flex-col items-center justify-center gap-3 text-center text-sm text-muted-foreground">
<p>No commanders here yet.</p>
<p>{t('commandHq.none')}</p>
<Link
to={commandHqNewPath(category)}
className="rounded-lg bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:opacity-90"
>
Start one
{t('commandHq.startOne')}
</Link>
</div>
)}
@@ -1,3 +1,5 @@
import type { PanelTranslationKey } from '../../locales';
/*
What the pane says before a new commander has been spoken to.
@@ -17,28 +19,22 @@
*/
export type GuideSection = {
title: string;
titleKey: PanelTranslationKey;
/** One paragraph per entry. Kept as an array so a section can grow without touching the renderer. */
body: string[];
bodyKeys: PanelTranslationKey[];
/** True until the copy is settled. Renders a marker; the whole point is that it is visible. */
provisional?: boolean;
};
export const NEW_COMMANDER_GUIDE: GuideSection[] = [
{
title: 'What a commander is',
body: [
'An agent that keeps working when you are not here — it waits for something to happen, acts on it, and reports back.',
'It is an ordinary conversation. You start by telling it what to watch for and what to do about it, in your own words.',
],
titleKey: 'commandHq.guide.whatIsIt',
bodyKeys: ['commandHq.guide.whatIsItBody1', 'commandHq.guide.whatIsItBody2'],
},
{
title: 'Starting one',
titleKey: 'commandHq.guide.starting',
provisional: true,
body: [
'Describe the job here, as you would to anybody. Say what it should watch, where, and what to do when it fires.',
'TODO: the concrete steps, once the creation flow is settled. This replaces "open the background-job dialog and arm a watcher", which nobody could be expected to guess.',
],
bodyKeys: ['commandHq.guide.startingBody1', 'commandHq.guide.startingBody2'],
},
{
// Requested 2026-08-22, deliberately NOT written yet: an example first prompt is a promise about what
@@ -48,17 +44,13 @@ export const NEW_COMMANDER_GUIDE: GuideSection[] = [
// Two or three, and they should differ in SHAPE rather than in subject: one where the trigger is
// obvious, one where it needs a conversation to pin down, one that is plainly not a commander at all so
// the boundary is visible.
title: 'Examples of a first message',
titleKey: 'commandHq.guide.examples',
provisional: true,
body: [
'TODO: two or three example opening prompts, once the creation flow is settled. They are what turns "describe the job" from an instruction into something a first-time user can act on.',
],
bodyKeys: ['commandHq.guide.examplesBody1'],
},
{
title: 'While it runs',
titleKey: 'commandHq.guide.whileRunning',
provisional: true,
body: [
'TODO: what the owner sees — where its cost shows, how to read what its spawned agents are doing, and how to stop it.',
],
bodyKeys: ['commandHq.guide.whileRunningBody1'],
},
];
@@ -213,5 +213,49 @@
"sidecar": "Sidecar",
"deleteNamed": "Delete {{name}}",
"updateTo": "Update to {{version}}"
},
"commandHq": {
"selectOne": "Select a commander, or start a new one.",
"startConversation": "Start the conversation",
"name": "Name",
"nameHint": "Applied to the conversation as it starts, instead of the harness naming it after your first message.",
"workingDirectory": "Working directory",
"cwdHint": "Where every command it runs resolves from, and where its history is kept. It cannot be moved later.",
"new": "New commander",
"spawned": "Spawned",
"deleteQ": "Delete?",
"uncategorised": "Uncategorised",
"loading": "Loading…",
"platformDeploys": "Platform deploys",
"endSession": "End session — releases it so it can be resumed elsewhere",
"confirmDeleteHint": "Confirm delete — removes the conversation too, not just the Command HQ entry",
"confirmDelete": "Confirm delete",
"cancelDelete": "Cancel delete",
"deleteCommander": "Delete commander",
"none": "No commanders here yet.",
"startOne": "Start one",
"confirmDeleteNamed": "Confirm deleting {{name}}",
"deleteNamed": "Delete {{name}}",
"categories": "Categories",
"noCategories": "No categories yet",
"newCategory": "New category",
"allCommanders": "All commanders",
"categoryName": "Category name",
"guide": {
"whatIsIt": "What a commander is",
"starting": "Starting one",
"examples": "Examples of a first message",
"whileRunning": "While it runs",
"whatIsItBody1": "An agent that keeps working when you are not here — it waits for something to happen, acts on it, and reports back.",
"whatIsItBody2": "It is an ordinary conversation. You start by telling it what to watch for and what to do about it, in your own words.",
"startingBody1": "Describe the job here, as you would to anybody. Say what it should watch, where, and what to do when it fires.",
"startingBody2": "TODO: the concrete steps, once the creation flow is settled. This replaces “open the background-job dialog and arm a watcher”, which nobody could be expected to guess.",
"examplesBody1": "TODO: two or three example opening prompts, once the creation flow is settled. They are what turns “describe the job” from an instruction into something a first-time user can act on.",
"whileRunningBody1": "TODO: what the owner sees — where its cost shows, how to read what its spawned agents are doing, and how to stop it.",
"draft": "draft"
},
"startHint": "It will look around this directory, then ask you what to watch for.",
"startBlocked": "{{missing}} — then this starts it.",
"confirmDeletingNamed": "Confirm deleting {{name}}"
}
}
@@ -213,5 +213,49 @@
"sidecar": "Sidecar",
"deleteNamed": "Eliminar {{name}}",
"updateTo": "Atualizar para {{version}}"
},
"commandHq": {
"selectOne": "Selecione um comandante ou inicie um novo.",
"startConversation": "Iniciar a conversa",
"name": "Nome",
"nameHint": "Aplicado à conversa quando esta começa, em vez de o harness a nomear a partir da sua primeira mensagem.",
"workingDirectory": "Diretório de trabalho",
"cwdHint": "A partir de onde todos os comandos que executa são resolvidos, e onde o histórico é guardado. Não pode ser movido depois.",
"new": "Novo comandante",
"spawned": "Iniciado",
"deleteQ": "Eliminar?",
"uncategorised": "Sem categoria",
"loading": "A carregar…",
"platformDeploys": "Implementações da plataforma",
"endSession": "Terminar sessão — liberta-a para poder ser retomada noutro lugar",
"confirmDeleteHint": "Confirmar eliminação — remove também a conversa, não apenas a entrada do Command HQ",
"confirmDelete": "Confirmar eliminação",
"cancelDelete": "Cancelar eliminação",
"deleteCommander": "Eliminar comandante",
"none": "Ainda não há comandantes aqui.",
"startOne": "Iniciar um",
"confirmDeleteNamed": "Confirmar a eliminação de {{name}}",
"deleteNamed": "Eliminar {{name}}",
"categories": "Categorias",
"noCategories": "Ainda não há categorias",
"newCategory": "Nova categoria",
"allCommanders": "Todos os comandantes",
"categoryName": "Nome da categoria",
"guide": {
"whatIsIt": "O que é um comandante",
"starting": "Como iniciar um",
"examples": "Exemplos de uma primeira mensagem",
"whileRunning": "Enquanto corre",
"whatIsItBody1": "Um agente que continua a trabalhar quando não está aqui — espera que algo aconteça, age em consequência e dá conta do resultado.",
"whatIsItBody2": "É uma conversa comum. Começa por lhe dizer o que deve vigiar e o que fazer quando isso acontecer, pelas suas próprias palavras.",
"startingBody1": "Descreva aqui o trabalho, como o faria a qualquer pessoa. Diga o que deve vigiar, onde, e o que fazer quando acontecer.",
"startingBody2": "TODO: os passos concretos, quando o fluxo de criação estiver definido. Isto substitui “abrir a caixa de diálogo de trabalhos em segundo plano e armar um vigilante”, que ninguém poderia adivinhar.",
"examplesBody1": "TODO: dois ou três exemplos de mensagens iniciais, quando o fluxo de criação estiver definido. São eles que transformam “descreva o trabalho” de uma instrução em algo sobre o qual um utilizador novo pode agir.",
"whileRunningBody1": "TODO: o que o proprietário vê — onde aparece o custo, como ler o que os agentes que ele inicia estão a fazer, e como o parar.",
"draft": "rascunho"
},
"startHint": "Vai observar este diretório e depois perguntar-lhe o que deve vigiar.",
"startBlocked": "{{missing}} — depois é isto que o inicia.",
"confirmDeletingNamed": "Confirmar a eliminação de {{name}}"
}
}