Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 32 additions & 9 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,18 @@ message out of the queue for editing; clicking a pending message does the same.
Escape stops the current turn. Pending messages then run in their original
order. A failed send preserves the queue for an explicit retry.

`/goal <objective>` starts work toward an objective. `/loop [interval] <prompt>`
repeats a prompt, immediately once and then at the given interval. Intervals use
`s`, `m` or `h`, from one second to 24 hours; the default is five minutes.
`/goal <objective>` starts work toward an objective. `/loop 2 <prompt>` runs two
consecutive iterations and stops. Counts range from 1 to 1000. A count written
as "2 iterations" or "2 itérations" in the prompt is also recognized.
`/loop 5m <prompt>` explicitly schedules repetition, with the delay counted
after each finished turn. Intervals use `s`, `m` or `h`, from one second to
24 hours. A loop without a count or interval is refused; there is no default timer.
Both commands belong to Boite and work with every driver. Goals and loops can
coexist with the agent's task list above the composer. Hover or click the task
row to expand it; its button also works from the keyboard and on a phone.
coexist with the agent's task list above the composer. The compact overlay shows
the current task and progress. Only a click expands it; updates and disclosure
do not resize the timeline. Completed tasks and goals fade out on the next user
prompt, and newly reported work brings the task list back. Loop details show
the latest 50 iterations with their outcome and up to 4000 characters of result.

The core owns this work, so switching threads or closing a client does not
cancel it. A goal continues through scheduled turns until the agent emits
Expand All @@ -143,6 +149,11 @@ Escape also pauses a loop between runs. The activity bar has pause, resume,
remove and manual goal completion controls. A restarted core preserves the
activity but requires an explicit resume.

Goal instructions are assembled only when invoking a driver. The journal stores
the visible `/goal` or `/loop` message with its kind and iteration in the text
part. The UI also cleans up goal prompts saved by older cores and hides standalone
completion/blocker markers, including partial markers during streaming.

Tasks come from ACP plans, Codex plan notifications or successful task tools
such as Claude's TodoWrite and TaskCreate/TaskUpdate. An agent that reports no
tasks gets no invented task list. Pi uses the same successful-tool observation.
Expand Down Expand Up @@ -237,6 +248,17 @@ from exact events to polling; without the second the focus guard never starts.

## Captures

`tests/e2e/header.test.ts` checks the shared header, sidebar folding and saved
state, project groups, machine menu ordering, and prompt navigation through a
paged, virtualized conversation. It captures desktop, phone and light-theme
layouts on the fake client. The shell suite checks that the thread controls sit
inside the same title bar and that dragging excludes editable controls.

The prompt outline uses at most 13 entries, keeping the first and last prompts
and seven around the reading position. Distant prompts are grouped behind a
keyboard-accessible list, so every loaded prompt remains reachable. Desktop
markers are 12 px apart; the compact activity panel sits 4 px above the composer.

The fake client is excluded from production bundles. Tests that need it must
use the Vite development server. `tests/e2e/settings.test.ts` starts and closes
one within the test process; the other end-to-end paths use a real temporary
Expand Down Expand Up @@ -312,10 +334,11 @@ panel, paragraph buffering, reasoning replacement, goal display and command
highlighting through the fake client. It writes desktop, phone and light-theme
captures under `tests/e2e/.artifacts/`.

Scheduled goal and loop prompts keep their execution instructions in `text` and
carry a separate optional `displayText` on the text part. The journal retains
both; drivers read the execution prompt. The UI shows the command and objective,
including when recalling a sent prompt, and hides standalone goal control markers.
Scheduled goal and loop prompts journal the command and objective in `text`, with
activity kind and iteration metadata. The core builds the execution instructions
when starting the driver. Older messages can carry `displayText`, which the UI
still honors when displaying or recalling a prompt. Terminal goal control markers
stay hidden; examples inside answer text or code fences remain visible.

Chat status uses two small receipts: core acceptance and the first nonempty
assistant activity. Agent protocols do not provide a literal read receipt.
Expand Down
Binary file added docs/images/chat-controls.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/prompt-navigation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions docs/model-switching.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ continuation of an image-bearing history explicitly.

## Storage and concurrent changes

The composer resolves an old `default` or `auto` model alias to the configured
provider preset for the next prompt when one is configured. A named model stays selected. Before sending
on an old thread, the UI verifies the preset against the account's model catalog
and saves it with the thread's selection revision. An unavailable preset or a
concurrent selection change refuses the send; it never silently runs the alias.
The picker also ignores saved presets containing these aliases.

Schema 9 adds `threads.session_generation`, `threads.selection_version` and
`turns.execution`. Existing native session IDs survive migration. Execution
snapshots record the target at acceptance, and both scheduler and driver use it.
Expand Down
18 changes: 14 additions & 4 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ export interface ImageAttachment {
}

export type MessagePart =
| { type: 'text'; text: string; /** User-facing command for internally scheduled prompts. */ displayText?: string }
| { type: 'text'; text: string; displayText?: string; activity?: { kind: 'goal' | 'loop'; iteration: number } }
/** An image the user sent with the prompt, journalled with the message. */
| { type: 'image'; mimeType: ImageMimeType; data: string; alt: string | null }
/** The model's reasoning as the provider streams it, folded in the UI. */
Expand Down Expand Up @@ -514,9 +514,19 @@ export interface AgentTask {
}

export interface ThreadActivity {
goal: { objective: string; status: 'active' | 'paused' | 'complete'; iterations: number; error: string | null } | null;
loop: { prompt: string; intervalMs: number; status: 'active' | 'paused'; iterations: number; nextRunAt: number | null; error: string | null } | null;
goal: { objective: string; status: 'active' | 'paused' | 'complete'; iterations: number; error: string | null; dismissed?: boolean } | null;
loop: { prompt: string; intervalMs: number; maxIterations?: number | null; status: 'active' | 'paused' | 'complete'; iterations: number; nextRunAt: number | null; error: string | null; history?: ActivityIteration[] } | null;
tasks: AgentTask[];
tasksDismissed?: boolean;
}

export interface ActivityIteration {
iteration: number;
turnId: TurnId;
status: 'running' | 'done' | 'error' | 'stopped';
summary: string;
startedAt: Timestamp;
finishedAt: Timestamp | null;
}

export interface Thread extends ThreadSummary {
Expand Down Expand Up @@ -902,7 +912,7 @@ export interface PairingGrant {
// ---------------------------------------------------------------------------

export interface RpcMethods {
'threads.activity.set': { params: { threadId: ThreadId; goal?: { objective: string } | null; loop?: { prompt: string; intervalMs: number } | null }; result: ThreadActivity };
'threads.activity.set': { params: { threadId: ThreadId; goal?: { objective: string } | null; loop?: { prompt: string; intervalMs: number; maxIterations?: number | null } | null }; result: ThreadActivity };
'threads.activity.control': { params: { threadId: ThreadId; kind: 'goal' | 'loop'; action: 'pause' | 'resume' | 'remove' | 'complete' }; result: ThreadActivity };
'quotas.list': { params: { refresh?: boolean }; result: AccountQuota[] };
'quotas.configure': { params: { accountId: AccountId; enabled: boolean }; result: AccountQuota[] };
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/activity-prompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** Instructions for automated turns stay out of the journal's user message. */
export function activityPrompt(kind: 'goal' | 'loop', text: string, iteration: number): string {
const objective = text.replace(/^\/(?:goal|loop)\s*/, '');
if (kind === 'loop') return `Iteration ${iteration}. Execute the following task once for this iteration. Boite owns the repetition and stopping count; do not start another loop or repeat the task yourself. Report the result of this iteration.\n${objective}`;
return `Work toward this goal: ${objective}\nContinue until the objective is achieved. When you have verified completion, end your answer with [BOITE_GOAL_COMPLETE] alone on its own line, outside code blocks. If blocked or waiting for user input, explain what is missing and end with [BOITE_GOAL_BLOCKED] alone on its own line, outside code blocks.\nTrack the work with your native planning tool (Codex: update_plan; Claude: TodoWrite or TaskCreate/TaskUpdate). Boite displays those task updates in this thread. Create the plan before working and update its statuses as you verify results. The Boite goal already exists; do not create a second goal or use legacy Boite todo tools.`;
}

export function activityResult(text: string): string {
return text.replace(/^\s*\[BOITE_GOAL_(?:COMPLETE|BLOCKED)\]\s*$/gm, '').trim();
}
Loading